@talismn/gandalf
v0.0.3
Published
SDK for verifying Gandalf auth tokens in Talisman APIs
Readme
@talismn/gandalf
SDK for verifying Gandalf auth tokens. Fully platform-agnostic — works with Cloudflare Workers, Node.js, Deno, Bun, Express, Hono, and any other JavaScript runtime.
- Offline JWT verification via JWKS
- In-memory JWKS cache (5-min TTL per isolate)
- ESM + CJS
- No dependency on the Fetch API
Requesttype
Installation
pnpm add @talismn/gandalfUsage
Verify an access token
import { verifyAccessToken, extractBearerToken } from "@talismn/gandalf";
export default {
async fetch(request: Request, env: Env) {
try {
const token = extractBearerToken(request.headers.get("Authorization"));
const auth = await verifyAccessToken(token, {
// Optional overrides:
// jwksUrl: env.GANDALF_JWKS_URL,
// issuer: env.GANDALF_ISSUER,
});
// auth.installId — the verified installation ID
// auth.claims — full JWT payload
return new Response(JSON.stringify({ data: "..." }));
} catch (err) {
if (err instanceof GandalfAuthError) {
return Response.json(
{ error: "unauthorized", message: err.message },
{ status: err.status }
);
}
throw err;
}
},
};Express / Node.js example
import { verifyAccessToken, extractBearerToken, GandalfAuthError } from "@talismn/gandalf";
app.use("/v1/*", async (req, res, next) => {
try {
const token = extractBearerToken(req.headers.authorization);
const auth = await verifyAccessToken(token);
req.auth = auth;
next();
} catch (err) {
if (err instanceof GandalfAuthError) {
return res.status(err.status).json({ error: "unauthorized", message: err.message });
}
next(err);
}
});Hono middleware example
import { Hono } from "hono";
import {
verifyAccessToken,
extractBearerToken,
GandalfAuthError,
type AuthContext,
} from "@talismn/gandalf";
type Env = { Bindings: { GANDALF_JWKS_URL: string } };
const app = new Hono<Env>();
// Auth middleware
app.use("/v1/*", async (c, next) => {
try {
const token = extractBearerToken(c.req.header("Authorization"));
const auth = await verifyAccessToken(token, {
jwksUrl: c.env.GANDALF_JWKS_URL,
});
c.set("auth", auth);
await next();
} catch (err) {
if (err instanceof GandalfAuthError) {
return c.json({ error: "unauthorized", message: err.message }, err.status);
}
throw err;
}
});
app.get("/v1/prices", (c) => {
const auth = c.get("auth") as AuthContext;
// auth.installId is available for logging, rate limiting, etc.
return c.json({ prices: [] });
});Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| jwksUrl | https://gandalf.talisman.xyz/.well-known/jwks.json | JWKS endpoint URL |
| issuer | https://gandalf.talisman.xyz | Expected JWT issuer claim |
| clockTolerance | 30 | Seconds of clock skew tolerance for exp/nbf checks |
JWKS Caching
The SDK caches JWKS in-memory per Worker isolate with a 5-minute TTL. When an unknown kid is encountered, jose's createRemoteJWKSet automatically refetches.
API Reference
verifyAccessToken(token, opts): Promise<AuthContext>
Verifies the JWT signature and claims, returns an AuthContext.
| Parameter | Type | Description |
|-----------|------|-------------|
| token | string | The raw JWT string |
| opts | VerifyOpts | Optional verification overrides |
extractBearerToken(authHeader): string
Extracts the raw JWT from an Authorization header value (e.g. "Bearer eyJ..."). Throws GandalfAuthError if the header is missing or malformed.
AuthContext
interface AuthContext {
installId: string; // from sub claim "install:<id>"
claims: JWTPayload; // full JWT payload
}Error Classes
| Class | Thrown by | Properties |
|-------|----------|------------|
| GandalfAuthError | verifyAccessToken | .status: number |
