Skip to content

JWT Authentication

createJwtAuthInterceptor verifies JSON Web Tokens from the Authorization: Bearer <token> header. It supports three key resolution strategies: JWKS, HMAC secret, and public key.

JWKS (JSON Web Key Set) is the recommended approach. The interceptor fetches and caches signing keys from the identity provider automatically:

typescript
import { createJwtAuthInterceptor } from '@connectum/auth';

const jwtAuth = createJwtAuthInterceptor({
  jwksUri: 'https://auth.example.com/.well-known/jwks.json',
  issuer: 'https://auth.example.com/',
  audience: 'my-api',
  maxTokenAge: '1h',
  claimsMapping: {
    roles: 'realm_access.roles',
    scopes: 'scope',
  },
});

Options

OptionTypeRequiredDescription
jwksUristringYes (if no publicKey/secret)URL to the JWKS endpoint
issuerstringNoExpected iss claim
audiencestringNoExpected aud claim
maxTokenAgestringNoMaximum acceptable token age (e.g. '1h', '30m')
claimsMappingobjectNoMap JWT claims to AuthContext fields

HMAC Secret

For simple setups and testing environments where tokens are signed with a shared secret:

typescript
const jwtAuth = createJwtAuthInterceptor({
  secret: process.env.JWT_SECRET,
  issuer: 'my-service',
});

WARNING

HMAC secrets require both the issuer and the verifier to know the secret. Use JWKS in production to avoid sharing signing keys.

Public Key

For asymmetric verification with a pre-loaded public key:

typescript
const publicKey = await crypto.subtle.importKey(
  'spki', keyData, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, true, ['verify'],
);

const jwtAuth = createJwtAuthInterceptor({ publicKey });

Key Resolution Priority

When multiple key sources are provided, resolution follows this priority:

jwksUri > publicKey > secret

At least one must be provided. If jwksUri is set, publicKey and secret are ignored.

Full Example

typescript
import { createServer } from '@connectum/core';
import { createDefaultInterceptors } from '@connectum/interceptors';
import { createJwtAuthInterceptor, createAuthzInterceptor } from '@connectum/auth';

const jwtAuth = createJwtAuthInterceptor({
  jwksUri: 'https://auth.example.com/.well-known/jwks.json',
  issuer: 'https://auth.example.com/',
  audience: 'my-api',
  maxTokenAge: '1h',
  claimsMapping: {
    roles: 'realm_access.roles',
    scopes: 'scope',
  },
});

const server = createServer({
  services: [routes],
  interceptors: [...createDefaultInterceptors(), jwtAuth],
});

await server.start();