new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Better Auth · Plugins · all subjects

jwt plugin

23 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

JWT plugin for JSON Web Token authentication

Better Auth includes a JWT plugin that provides JSON Web Token authentication for services.

JWT plugin installation steps

To install the JWT plugin: (1) Add `import { jwt } from 'better-auth/plugins'` and call `jwt()` in the `plugins` array of `betterAuth()` config. (2) Run `npx auth migrate` or `npx auth generate` to add necessary fields and tables to the database. (3) Add `import { jwtClient } from 'better-auth/client/plugins'` and call `jwtClient()` in the `plugins` array of `createAuthClient()`.

Retrieve JWT token using client plugin

Call `await authClient.token()` to retrieve a JWT token. This returns an object with `{ data, error }`. If successful, `data.token` contains the JWT token string that can be used for authenticated requests to external services.

Retrieve JWT token from set-auth-jwt header

When calling `authClient.getSession()` with a `fetchOptions` callback, a JWT is returned in the `set-auth-jwt` response header. Access it with `const jwt = ctx.response.headers.get('set-auth-jwt')`.

JWT JWKS endpoint location

The JWKS public key can be fetched from the `/api/auth/jwks` endpoint. The endpoint returns an object with a `keys` array containing key objects with fields: `crv` (curve), `x` (public key coordinate), `kty` (key type), and `kid` (key ID used to sign the JWT).

JWKS caching strategy

The JWKS public key is not subject to frequent changes and can be cached indefinitely. The key ID (`kid`) used to sign a JWT is included in the token header. If a JWT with a different `kid` is received, it is recommended to fetch the JWKS again.

Verify JWT token using jose with local JWKS

Example code for verifying a JWT token using the jose library with a locally stored JWKS: ```ts import { jwtVerify, createLocalJWKSet } from 'jose' async function validateToken(token: string) { try { const storedJWKS = { keys: [{ //... }] }; const JWKS = createLocalJWKSet({ keys: storedJWKS.data?.keys!, }) const { payload } = await jwtVerify(token, JWKS, { issuer: 'http://localhost:3000', audience: 'http://localhost:3000', }) return payload } catch (error) { console.error('Token validation failed:', error) throw error } } const token = 'your.jwt.token' const payload = await validateToken(token) ```

Disable JWT token endpoint for OAuth provider mode

When making your system OAuth compliant (such as with OIDC or MCP plugins), disable the `/token` endpoint and disable setting the JWT header. Add `disabledPaths: ["/token"]` to `betterAuth()` config and set `disableSettingJwtHeader: true` in the `jwt()` plugin options.

Custom JWKS remote URL configuration

To use a remote JWKS URL instead of the `/jwks` endpoint, configure: `jwt({ jwks: { remoteUrl: 'https://example.com/.well-known/jwks.json', keyPairConfig: { alg: 'ES256' } } })`. You must specify which asymmetric algorithm is used for signing via `keyPairConfig.alg`.

Custom JWKS path configuration

By default, the JWKS endpoint is at `/jwks`. Customize it using `jwt({ jwks: { jwksPath: '/.well-known/jwks.json' } })`. When using a custom path on the server, the client must be configured with the same path: `jwtClient({ jwks: { jwksPath: '/.well-known/jwks.json' } })`. The client and server paths must match or the client will fail to fetch JWKS.

JWKS table schema

The JWT plugin creates a `jwks` table with the following fields: `id` (string, primary key, unique identifier for each web key), `publicKey` (string, the public part of the web key), `privateKey` (string, the private part of the web key), `createdAt` (Date, timestamp of when the web key was created), `expiresAt` (Date, optional, timestamp of when the web key expires).

Session cookie cache JWT signing

Set `sessionCookieCache: true` in the `jwt()` plugin options to sign JWT session cookie cache values with the plugin's locally managed keys. This requires `session.cookieCache.strategy` to be set to `'jwt'` in the auth configuration.

JWT key pair algorithm options

The algorithm used for key pair generation is configured via `jwt({ jwks: { keyPairConfig: { alg, ... } } })`. Default is EdDSA with Ed25519 curve. Available algorithms: EdDSA (with optional `crv`: Ed25519 or Ed448, default Ed25519), ES256 (no additional properties), RSA256 (optional `modulusLength` number, default 2048), PS256 (optional `modulusLength` number, default 2048), ECDH-ES (optional `crv`: P-256, P-384, or P-521, default P-256), ES512 (no additional properties).

Disable private key encryption

By default, private keys are encrypted using AES256 GCM. Disable this with `jwt({ jwks: { disablePrivateKeyEncryption: true } })`. It is recommended to keep the private key encrypted for security reasons.

JWT key rotation configuration

Enable key rotation by setting `jwt({ jwks: { rotationInterval: 60 * 60 * 24 * 30, gracePeriod: 60 * 60 * 24 * 30 } })`. `rotationInterval` is the interval in seconds to rotate the key pair (default undefined, disabled). `gracePeriod` is the period in seconds to keep the old key pair valid after rotation (default 30 days). This allows clients to verify tokens signed by the old key pair.

Modify JWT payload content

By default, the entire user object is added to the JWT payload. Customize this using `jwt({ jwt: { definePayload: ({user}) => ({ id: user.id, email: user.email, role: user.role }) } })`. The `definePayload` function receives the user object and returns a custom payload object.

JWT issuer, audience, and expiration configuration

Configure JWT claims with `jwt({ jwt: { issuer: 'https://example.com', audience: 'https://example.com', expirationTime: '1h', getSubject: (session) => session.user.email } })`. If not specified, BASE_URL is used as both issuer and audience. Default expiration time is 15 minutes. Default subject is the user ID. `getSubject` is a function that receives the session object.

Custom JWT adapter for JWKS storage

Override default JWKS database storage by providing a custom adapter: `jwt({ adapter: { getJwks: async (ctx) => { return await yourCustomStorage.getAllKeys() }, createJwk: async (ctx, webKey) => { return await yourCustomStorage.createKey(webKey) } } })`. This allows storing JWKS in Redis, external services, or in-memory storage.

Localized custom signing example

Advanced JWT signing using a localized approach: ```ts jwt({ jwks: { remoteUrl: 'https://example.com/.well-known/jwks.json', keyPairConfig: { alg: 'EdDSA', }, }, jwt: { sign: async (jwtPayload: JWTPayload) => { return await new SignJWT(jwtPayload) .setProtectedHeader({ alg: 'EdDSA', kid: process.env.currentKid, typ: 'JWT', }) .sign(process.env.clientPrivateKey); }, }, }) ``` When using a localized approach, ensure the server uses the latest private key when rotated and may need to be restarted depending on deployment.

Remote custom signing example with KMS

Advanced JWT signing using a remote Key Management Service (such as Google KMS, Amazon KMS, or Azure Key Vault): ```ts jwt({ jwks: { remoteUrl: 'https://example.com/.well-known/jwks.json', keyPairConfig: { alg: 'ES256', }, }, jwt: { sign: async (jwtPayload: JWTPayload) => { const headers = JSON.stringify({ kid: '123', alg: 'ES256', typ: 'JWT' }) const payload = JSON.stringify(jwtPayload) const encodedHeaders = Buffer.from(headers).toString('base64url') const encodedPayload = Buffer.from(payload).toString('base64url') const hash = createHash('sha256') const data = `${encodedHeaders}.${encodedPayload}` hash.update(Buffer.from(data)) const digest = hash.digest() const sig = await remoteSign(digest) const jwt = `${data}.${sig}` return jwt }, }, }) ``` When using remote approach, verify the payload is unchanged after transit using integrity validation like CRC32 or SHA256 checks.

JWT plugin not a session replacement

The JWT plugin is not meant as a replacement for the session. It is meant to be used for services that require JWT tokens. For authentication using JWT tokens, check out the Bearer Plugin instead.

Session cookie cache JWT strategy

If `session.cookieCache.strategy` is set to `'jwt'`, the `session_data` cookie uses HS256 with the Better Auth secret by default. To make that cookie-cache JWT verifiable with the JWT plugin's JWKS endpoint, set `sessionCookieCache: true` on the `jwt()` plugin configuration.

JWT /token endpoint retrieval

To get a JWT token via the `/token` endpoint, make a request to `/api/auth/token` with an Authorization header containing a Bearer token. The endpoint returns a JSON response with the format `{ "token": "ey..." }` containing the JWT token.

Give your agent this brain