Skip to content

Tokens & TTL

Artifact Expirations (TTL)

Specifies the Time-To-Live (TTL) values that shall be applied to various artifacts within the authorization server. Every static value and every synchronous callback result MUST be a positive safe integer number of seconds (Number.isSafeInteger(value) && value > 0). Zero, negative, fractional, NaN, infinite, and unsafe integer values are rejected. TypeScript represents this contract as number, so these constraints are enforced when the Provider is constructed for static values and whenever a configured callback is evaluated for dynamic values.

recommendation: Token TTL values should be set to the minimum duration necessary for the intended use case to minimize security exposure.

recommendation: For refresh tokens requiring extended lifetimes, consider utilizing the rotateRefreshToken configuration option, which extends effective token lifetime through rotation rather than extended initial TTL values.

default value:

const ttl: {
AccessToken?:
| number
| ((ctx: KoaContextWithOIDC, token: AccessToken, client: Client) => number)
| undefined;
AuthorizationCode?:
| number
| ((ctx: KoaContextWithOIDC, code: AuthorizationCode, client: Client) => number)
| undefined;
BackchannelAuthenticationRequest?:
| number
| ((ctx: KoaContextWithOIDC, request: BackchannelAuthenticationRequest, client: Client) => number)
| undefined;
ClientCredentials?:
| number
| ((ctx: KoaContextWithOIDC, token: ClientCredentials, client: Client) => number)
| undefined;
DeviceCode?: number | ((ctx: KoaContextWithOIDC, code: DeviceCode, client: Client) => number) | undefined;
Grant?: number | ((ctx: KoaContextWithOIDC, grant: Grant) => number) | undefined;
IdToken?: number | ((ctx: KoaContextWithOIDC, token: IdToken, client: Client) => number) | undefined;
Interaction?: number | ((ctx: KoaContextWithOIDC, interaction: Interaction) => number) | undefined;
PreAuthorizedCode?: number | ((ctx: KoaContextWithOIDC, code: PreAuthorizedCode) => number) | undefined;
RefreshToken?:
| number
| ((ctx: KoaContextWithOIDC, token: RefreshToken, client: Client) => number)
| undefined;
Session?: number | ((ctx: KoaContextWithOIDC, session: Session) => number) | undefined;
[key: string]: unknown;
} = {
AccessToken: function AccessToken(ctx, token, client) {
return token.resourceServer?.accessTokenTTL || 60 * 60; // 1 hour in seconds
},
AuthorizationCode: function AuthorizationCode(ctx, code, client) {
return 60; // 1 minute in seconds
},
BackchannelAuthenticationRequest: function BackchannelAuthenticationRequest(ctx, request, client) {
if (ctx?.oidc?.params.requested_expiry) {
return Math.min(10 * 60, +ctx.oidc.params.requested_expiry); // 10 minutes in seconds or requested_expiry, whichever is shorter
}
return 10 * 60; // 10 minutes in seconds
},
ClientCredentials: function ClientCredentials(ctx, token, client) {
return token.resourceServer?.accessTokenTTL || 10 * 60; // 10 minutes in seconds
},
DeviceCode: function DeviceCode(ctx, deviceCode, client) {
return 10 * 60; // 10 minutes in seconds
},
Grant: function Grant(ctx, grant) {
return 14 * 24 * 60 * 60; // 14 days in seconds
},
IdToken: function IdToken(ctx, token, client) {
return 60 * 60; // 1 hour in seconds
},
Interaction: function Interaction(ctx, interaction) {
return 60 * 60; // 1 hour in seconds
},
PreAuthorizedCode: function PreAuthorizedCode(ctx, code) {
return 10 * 60; // 10 minutes in seconds
},
RefreshToken: function RefreshToken(ctx, token, client) {
if (
ctx?.oidc?.entities.RotatedRefreshToken
&& client.applicationType === 'web'
&& client.clientAuthMethod === 'none'
&& !token.isSenderConstrained()
) {
// Non-Sender Constrained SPA RefreshTokens do not have infinite expiration through rotation
return ctx.oidc.entities.RotatedRefreshToken.remainingTTL;
}
return 14 * 24 * 60 * 60; // 14 days in seconds
},
Session: function Session(ctx, session) {
return 14 * 24 * 60 * 60; // 14 days in seconds
}
};

Example: (Click to expand) To resolve a ttl on runtime for each new token.

Configure ttl for a given token type with a function like so, this must return a value, not a Promise.

{
ttl: {
AccessToken(ctx, token, client) {
// Return a positive safe integer number of seconds for the given token (second
// argument). The associated client is passed as a third argument.
// Tip: if the values are entirely client based memoize the results
return resolveTTLfor(token, client);
},
},
}

Session-Bound Token Expiration

Specifies a helper function that shall be invoked to determine whether authorization codes, device codes, or authorization-endpoint-returned opaque access tokens shall be bound to the end-user session. When session binding is enabled, this policy shall be applied to all opaque tokens issued from the authorization code, device code, or subsequent refresh token exchanges. When artifacts are session-bound, their originating session will be loaded by its unique identifier every time the artifacts are encountered. Session-bound artifacts shall be effectively revoked when the end-user logs out, providing automatic cleanup of token state upon session termination.

true binds the artifact to the current session; false leaves it usable after logout and marks the client authorization as persisting logout. The default returns false only when the source includes the offline_access scope.

default value:

async function expiresWithSession(
_ctx: KoaContextWithOIDC,
source: AccessToken | AuthorizationCode | DeviceCode,
): Promise<boolean> {
return !source.scopes.has('offline_access');
}

Refresh Token Issuance Policy

Specifies a helper function that shall be invoked to determine whether a refresh token shall be issued during token endpoint operations. This function enables policy-based control over refresh token issuance according to authorization server requirements, client capabilities, and granted scope values.

true issues a refresh token and false does not. The default requires both the refresh_token grant type and the offline_access scope.

default value:

async function issueRefreshToken(
_ctx: KoaContextWithOIDC,
client: Client,
source: AuthorizationCode | DeviceCode | BackchannelAuthenticationRequest | PreAuthorizedCode,
): Promise<boolean> {
return (
client.grantTypeAllowed('refresh_token')
&& source.scopes.has('offline_access')
);
}

Example: (Click to expand) To always issue a refresh token (cont.)

(cont.) if a client has the grant allowed and scope includes offline_access or the client is a public web client doing code flow. Configure issueRefreshToken like so

async issueRefreshToken(ctx, client, code) {
if (!client.grantTypeAllowed('refresh_token')) {
return false;
}
return code.scopes.has('offline_access') || (client.applicationType === 'web' && client.clientAuthMethod === 'none');
}

Refresh Token Rotation Policy

Specifies the refresh token rotation policy that shall be applied by the authorization server when refresh tokens are used. This configuration determines whether and under what conditions refresh tokens shall be rotated. Supported values include:

  • false - refresh tokens shall not be rotated and their initial expiration date is final
  • true - refresh tokens shall be rotated when used, with the current token marked as consumed and a new one issued with new TTL; when a consumed refresh token is encountered an error shall be returned and the whole token chain (grant) is revoked
  • function - a function returning true/false that shall be invoked to determine whether rotation should occur based on request context and authorization server policy

The default configuration value implements a sensible refresh token rotation policy that:

  • only allows refresh tokens to be rotated (have their TTL prolonged by issuing a new one) for one year
  • otherwise always rotates public client tokens that are not sender-constrained
  • otherwise only rotates tokens if they’re being used close to their expiration (>= 70% TTL passed)

The RefreshToken and Client are available as ctx.oidc.entities.RefreshToken and ctx.oidc.entities.Client. true consumes the presented token and issues a rotated refresh token; false continues without rotation. A configured literal Boolean applies that decision without invoking a function.

default value:

function rotateRefreshToken(ctx: KoaContextWithOIDC): CanBePromise<boolean> {
const { RefreshToken: refreshToken, Client: client } = ctx.oidc.entities;
// cap the maximum amount of time a refresh token can be
// rotated for up to 1 year, afterwards its TTL is final
if (refreshToken.totalLifetime() >= 365.25 * 24 * 60 * 60) {
return false;
}
// rotate non sender-constrained public client refresh tokens
if (
client.clientAuthMethod === 'none'
&& !refreshToken.isSenderConstrained()
) {
return true;
}
// rotate if the token is nearing expiration (it's beyond 70% of its lifetime)
return refreshToken.ttlPercentagePassed() >= 70;
}

Additional Access Token Claims

Specifies a helper function that shall be invoked to add additional claims to Access Tokens during the token issuance process. For opaque Access Tokens, the returned claims shall be stored in the authorization server storage under the extra property and shall be returned by the introspection endpoint as top-level claims. For JWT-formatted Access Tokens, the returned claims shall be included as top-level claims within the JWT payload. Claims returned by this function will not overwrite pre-existing top-level claims in the token.

default value:

async function extraTokenClaims(
ctx: KoaContextWithOIDC,
token: AccessToken | ClientCredentials,
): Promise<UnknownObject | undefined> {
return undefined;
}

Example: (Click to expand) To add an arbitrary claim to an Access Token.

{
async extraTokenClaims(ctx, token) {
return {
'urn:idp:example:foo': 'bar',
};
}
}

Specifies the entropy configuration for opaque token generation. The value shall be an integer (or a function returning an integer) that determines the cryptographic strength of generated opaque tokens. The resulting opaque token length shall be calculated as Math.ceil(i / Math.log2(n)) where i is the specified bit count and n is the number of symbols in the encoding alphabet (64 characters in the base64url character set used by this implementation).

default value:

const bitsOfOpaqueRandomness: number | ((ctx: KoaContextWithOIDC, model: BaseModel) => number) = 256;

Example: (Click to expand) To have e.g. Refresh Tokens values longer than Access Tokens.

function bitsOfOpaqueRandomness(ctx, token) {
if (token.kind === 'RefreshToken') {
return 384;
}
return 256;
}

Specifies customizer functions that shall be invoked immediately before issuing structured Access Tokens to enable modification of token headers and payload claims according to authorization server policy. These functions shall be called during the token formatting process to apply deployment-specific customizations to the token structure before signing. Customize the supplied jwt.header and jwt.payload objects in place; a customizer’s return value is ignored.

default value:

const customizers: {
jwt?:
| ((
ctx: KoaContextWithOIDC,
token: AccessToken | ClientCredentials,
parts: JWTStructured,
) => CanBePromise<void>)
| undefined;
} = {
jwt: undefined
};

Example: (Click to expand) To add additional headers and payload claims to a jwt format Access Token.

{
customizers: {
async jwt(ctx, token, jwt) {
jwt.header = { foo: 'bar' };
jwt.payload.foo = 'bar';
}
}
}