Authorization ‐ OAuth2 ‐ Tuto 2 ‐ Core Builder Methods - demingongo/kaapi GitHub Wiki

Core Builder Methods in @kaapi/oauth2-auth-design

This section introduces the core methods shared across all builders in @kaapi/oauth2-auth-design. These methods are the foundation for defining, customizing, and composing OAuth2 and OpenID Connect (OIDC) authentication flows.

🔧 Supported Builders

The following grant types and their corresponding builders are currently supported:

Grant Type OAuth2 Builder OIDC Builder
Authorization Code OAuth2AuthorizationCodeBuilder OIDCAuthorizationCodeBuilder
Client Credentials OAuth2ClientCredentialsBuilder OIDCClientCredentialsBuilder
Device Authorization OAuth2DeviceAuthorizationBuilder OIDCDeviceAuthorizationBuilder

create

Every builder is instantiated using the static create method. It accepts an options object, including a logger, the only option that can only be set at creation time.

The logger must implement the ILogger interface from @kaapi/kaapi.

import { createLogger } from '@kaapi/kaapi';
import { OAuth2AuthorizationCodeBuilder } from '@kaapi/oauth2-auth-design';

const logger = createLogger();

const builder = OAuth2AuthorizationCodeBuilder.create({ logger });

strategyName

Defines the name of the strategy. If omitted, a default name is automatically assigned based on the builder type.

const authDesign = OAuth2AuthorizationCodeBuilder
  .create()
  .strategyName('oauth2-auth-code')
  .build();

console.log(authDesign.getStrategyName()); // oauth2-auth-code

Default Strategy Names

Builder Default Name
OAuth2AuthorizationCodeBuilder oauth2-authorization-code
OIDCAuthorizationCodeBuilder oauth2-authorization-code
OAuth2ClientCredentialsBuilder oauth2-client-credentials
OIDCClientCredentialsBuilder oauth2-client-credentials
OAuth2DeviceAuthorizationBuilder oauth2-device-authorization
OIDCDeviceAuthorizationBuilder oauth2-device-authorization

setDescription

Sets a description for the strategy for documentation purposes.

OAuth2ClientCredentialsBuilder
  .create()
  .setDescription('Client credentials grant flow');

setScopes

Defines the available scopes and their descriptions for documentation purposes.

OAuth2ClientCredentialsBuilder.create().setScopes({
  'read:data': 'Allows the client to retrieve or query data from the service.',
  'write:data': 'Allows the client to create or update data in the service.'
});

addClientAuthenticationMethod

Adds one or more client authentication methods to the strategy. These are used to validate incoming token requests.

Supported Methods

  • ClientSecretPost
  • ClientSecretBasic
  • ClientSecretJwt
  • PrivateKeyJwt
  • NoneAuthMethod
.addClientAuthenticationMethod(new ClientSecretBasic())
.addClientAuthenticationMethod(new ClientSecretPost())
.addClientAuthenticationMethod(new NoneAuthMethod())

ClientSecretJwt

Authenticates clients using a JWT signed with a shared secret.

Supported Algorithms

  • HS256 (default)
  • HS384
  • HS512

Example

import { ClientSecretJwt } from '@kaapi/oauth2-auth-design';

const clientAuthMethod = new ClientSecretJwt()
  .addAlgorithm(ClientSecretJwt.algo.HS256)
  .addAlgorithm(ClientSecretJwt.algo.HS384)
  .addAlgorithm(ClientSecretJwt.algo.HS512)
  .getClientSecret(async (clientId, decoded, clientAssertion) => {
    return 'super-secret-value';
  });

PrivateKeyJwt

Authenticates clients using a JWT signed with a private key.

Supported Algorithms

  • RSA: RS256, RS384, RS512
  • RSA-PSS: PS256, PS384, PS512
  • EC: ES256, ES384, ES512
  • EdDSA: Ed25519

Example

import { PrivateKeyJwt } from '@kaapi/oauth2-auth-design';

const clientAuthMethod = new PrivateKeyJwt()
  .addAlgorithm(PrivateKeyJwt.algo.RS256)
  .addAlgorithm(PrivateKeyJwt.algo.PS256)
  .addAlgorithm(PrivateKeyJwt.algo.ES256)
  .getPublicKeyForClient(async (clientId, decoded, clientAssertion) => {
    return {
      kty: 'RSA',
      kid: 'client-key-1',
      use: 'sig',
      alg: 'RS256',
      n: '...',
      e: '...'
    };
  });

setTokenType

Defines the type of access token to issue. Two types are supported:

  • BearerToken – Default; requires no additional proof.
  • DPoPToken – Requires cryptographic proof with each use.
.setTokenType(new BearerToken())

DPoPToken

A proof-of-possession token that mitigates token replay attacks.

Example with TTL and Replay Detection

import { DPoPToken, createInMemoryReplayStore } from '@kaapi/oauth2-auth-design';

const tokenType = new DPoPToken()
  .setTTL(300)
  .setReplayDetector(createInMemoryReplayStore());

Example with Custom Validation

const tokenType = new DPoPToken()
  .validateTokenRequest(async (req, ttl) => {
    return {
      isValid: false,
      message: 'Invalid DPoP token'
    };
  });

jwksRoute

Customizes the JWKS endpoint. You can change the path and/or provide a custom handler.

.jwksRoute(route => route
  .setPath('/.well-known/jwks.json')
  .validate((params, request, h) => {
    return params.jwks;
  })
)

Default path: /oauth2/keys


tokenRoute

Defines the token endpoint behavior, including path and token generation logic.

import { OAuth2ErrorCode, OAuth2TokenResponse } from '@kaapi/oauth2-auth-design';

.tokenRoute(route => route
  .setPath('/oauth2/token')
  .generateToken(async (params, request) => {
    if (!params.clientSecret) {
      return {
        error: OAuth2ErrorCode.INVALID_REQUEST,
        error_description: 'Missing client_secret'
      };
    }

    return new OAuth2TokenResponse({ access_token: 'generated-access-token' })
      .setExpiresIn(params.ttl)
      .setScope([])
      .setTokenType(params.tokenType);
  })
)

Default path: /oauth2/token


setTokenTTL

Sets the default time-to-live (TTL) for issued tokens, in seconds.

.setTokenTTL(3600)

This value is passed to the generateToken handler in tokenRoute.


setJwksKeyStore

Defines the JWKS key store implementing the JwksKeyStore interface. This store is responsible for:

  • Retrieving the active private key
  • Accessing public keys
  • Persisting new key pairs

For development and testing, use the in-memory store provided by createInMemoryKeyStore.

import { OAuth2AuthorizationCodeBuilder, createInMemoryKeyStore } from '@kaapi/oauth2-auth-design';

OAuth2AuthorizationCodeBuilder
  .create()
  .setJwksKeyStore(createInMemoryKeyStore());

setJwksRotatorOptions

Configures automatic key rotation. Accepts:

  • intervalMs: rotation interval in milliseconds
  • timestampStore: an implementation of JwksRotationTimestampStore to track the last rotation

Example with in-memory stores:

import { OAuth2AuthorizationCodeBuilder, createInMemoryKeyStore } from '@kaapi/oauth2-auth-design';

const authCodeFlow = OAuth2AuthorizationCodeBuilder
  .create()
  .setJwksKeyStore(createInMemoryKeyStore())
  .setJwksRotatorOptions({
    intervalMs: 7.862e+9, // 91 days
    timestampStore: createInMemoryKeyStore()
  })
  .build();

// Initial rotation check
authCodeFlow.checkAndRotateKeys().catch(console.error);

// Periodic rotation check (every hour)
setInterval(() => {
  authCodeFlow.checkAndRotateKeys().catch(console.error);
}, 3600 * 1000);

The checkAndRotateKeys method evaluates whether a new key pair should be generated and updates the key store accordingly.


setPublicKeyExpiry

Sets the time-to-live (TTL) for public keys in seconds. Useful when combined with key rotation to ensure old keys expire.

.setPublicKeyExpiry(8.64e+6) // 100 days

Example with rotation:

.setJwksRotatorOptions({
  intervalMs: 7.862e+9, // 91 days
  timestampStore: createInMemoryKeyStore()
})
.setPublicKeyExpiry(8.64e+6); // 100 days

useAccessTokenJwks

Enables signing and verification of access tokens using the JWKS private key.

.useAccessTokenJwks(true)
.tokenRoute(route =>
  route.generateToken(async (params, _req) => {
    const { token } = await params.createJwtAccessToken({
      sub: 'user-id',
      // additional claims...
    });

    return { access_token: token };
  })
)
.validate(async (request, { jwtAccessTokenPayload, token }) => {
  // custom validation logic
});

validate

Defines the logic to validate incoming access tokens. Typically used to fetch and verify user credentials.

.validate(async (request, { token }) => {
  // Verify that the token is known, valid, and not expired
  // (for example by checking a database, cache, or JWT claims)

  const isValidToken = Boolean(token); // replace with your actual validation logic

  if (!isValidToken) {
    return { isValid: false };
  }

  // Attach credentials that will be available in request.auth.credentials
  return {
    isValid: true,
    credentials: {
      user: {
        sub: '12345',
        name: 'John Doe',
        email: '[email protected]'
      }
    }
  };
});

build

Finalizes the builder configuration and returns a plugin that can be registered with your Kaapi app.

import { Kaapi } from '@kaapi/kaapi';
import { OAuth2AuthorizationCodeBuilder } from '@kaapi/oauth2-auth-design';

const authCodeFlow = OAuth2AuthorizationCodeBuilder
  .create()
  // additional configuration...
  .build();

const app = new Kaapi({
  port: 3000,
  host: 'localhost',
  docs: {
    host: { url: 'http://localhost:3000' }
  }
});

await app.extend(authCodeFlow);

additionalConfiguration

This method is specific to OIDC builders and allows you to override the OpenID metadata served at /.well-known/openid-configuration. It's useful for customizing supported scopes, claims, endpoints, and token types.

Example: Overriding Metadata

OIDCAuthorizationCodeBuilder
  .create()
  .additionalConfiguration({
    claims_supported: ['sub', 'name', 'email']
  });

Example: Adding DPoP Token Metadata

If you're using the DPoP token type, you can inject its metadata directly:

import { DPoPToken, OIDCAuthorizationCodeBuilder } from '@kaapi/oauth2-auth-design';

const tokenType = new DPoPToken().setTTL(300);

OIDCAuthorizationCodeBuilder
  .create()
  .additionalConfiguration({
    ...tokenType.configuration
  });

This ensures your OpenID configuration reflects support for DPoP tokens.