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
maxTokenAgestring | numberNoMaximum acceptable token age (e.g. '1h', '30m', or seconds)
algorithmsstring[]NoAllowed signature algorithms (e.g. ['RS256'])
claimsMappingobjectNoMap JWT claims to AuthContext fields
skipMethodsstring[]NoMethods to skip authentication for (e.g. 'Service/Method', 'Service/*')
propagateHeadersbooleanNoForward auth context as x-auth-* headers to downstream services (default false)

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();