Skip to content

Error Handling

@authrim/server provides a structured error system for token validation, DPoP, JWKS, introspection, revocation, Step-Up, and configuration failures. validateToken() returns normal token-validation failures as { data: null, error }; framework adapters and helper utilities use AuthrimServerError metadata to build HTTP responses.

Operations that need HTTP response metadata use AuthrimServerError:

import { AuthrimServerError } from '@authrim/server';
const result = await server.validateToken(accessToken);
if (result.error) {
const error = new AuthrimServerError(
result.error.code as any,
result.error.message,
);
console.log(error.code); // e.g., 'token_expired'
console.log(error.message); // Human-readable description
console.log(error.meta); // { httpStatus, transient, retryable, wwwAuthenticateError }
}
PropertyTypeDescription
codestringMachine-readable error code (see table below)
messagestringHuman-readable error description
metaAuthrimServerErrorMetaClassification metadata
PropertyTypeDescription
httpStatusnumberRecommended HTTP status code for the response
transientbooleanWhether the error is temporary and may resolve on its own
retryablebooleanWhether the operation can be safely retried
wwwAuthenticateErrorstring | undefinedError code for the WWW-Authenticate header (RFC 6750)
CodeHTTP StatusTransientRetryableDescription
invalid_token401NoNoToken is invalid (general validation failure)
token_expired401NoNoToken has expired (exp claim)
token_not_yet_valid401NoNoToken is not yet valid (nbf claim)
token_malformed401NoNoToken cannot be decoded or parsed
signature_invalid401NoNoJWT signature verification failed
algorithm_mismatch401NoNoToken algorithm does not match expected algorithms
CodeHTTP StatusTransientRetryableDescription
invalid_issuer401NoNoToken issuer does not match expected issuer
invalid_audience401NoNoToken audience does not include expected audience
CodeHTTP StatusTransientRetryableDescription
jwks_fetch_error502YesYesFailed to fetch the JWKS document
jwks_key_not_found401NoNoNo matching key found in JWKS for the token’s kid
jwks_key_ambiguous401NoNoMultiple keys match the token’s kid
jwks_key_import_error500NoNoFailed to import a JWK for signature verification
CodeHTTP StatusTransientRetryableDescription
dpop_proof_missing401NoNoDPoP proof header is missing
dpop_proof_invalid401NoNoDPoP proof is malformed or invalid
dpop_proof_signature_invalid401NoNoDPoP proof signature verification failed
dpop_method_mismatch401NoNoHTTP method in DPoP proof does not match request
dpop_uri_mismatch401NoNoURI in DPoP proof does not match request URL
dpop_ath_mismatch401NoNoAccess token hash in DPoP proof does not match
dpop_binding_mismatch401NoNoDPoP proof key does not match token binding
dpop_iat_expired401NoNoDPoP proof iat is too old
dpop_nonce_required401NoNoServer requires a DPoP nonce but none was provided
CodeHTTP StatusTransientRetryableDescription
introspection_error502YesYesToken introspection request failed
revocation_error502YesYesToken revocation request failed
CodeHTTP StatusTransientRetryableDescription
configuration_error500NoNoInvalid SDK configuration
provider_error500NoNoProvider implementation error
CodeHTTP StatusTransientRetryableDescription
network_error502YesYesNetwork request failed
timeout_error504YesYesRequest timed out

Generates an RFC 6750 compliant WWW-Authenticate header value:

import { buildWwwAuthenticateHeader } from '@authrim/server';
const header = buildWwwAuthenticateHeader(error, 'my-api');
// Example output:
// Bearer realm="my-api", error="invalid_token", error_description="Token has expired"

Options:

OptionTypeDescription
realmstringRealm value for the header

Generates a JSON error body suitable for HTTP responses:

import { buildErrorResponse } from '@authrim/server';
const body = buildErrorResponse(error);
// {
// error: 'invalid_token',
// error_description: 'Token has expired'
// }

Generates response headers including WWW-Authenticate:

import { buildErrorHeaders } from '@authrim/server';
const headers = buildErrorHeaders(error, { realm: 'my-api' });
// {
// 'WWW-Authenticate': 'Bearer realm="my-api", error="invalid_token", ...',
// 'Content-Type': 'application/json'
// }

All framework adapters accept an onError callback for logging and monitoring:

import { authrimMiddleware } from '@authrim/server/adapters/express';
app.use(
'/api',
authrimMiddleware(server, {
realm: 'my-api',
onError: (error) => {
// Log to your monitoring service
logger.warn({
code: error.code,
message: error.message,
httpStatus: error.httpStatus,
});
// Track metrics
metrics.increment('auth.error', { code: error.code });
},
}),
);
import {
AuthrimServerError,
buildErrorResponse,
buildErrorHeaders,
} from '@authrim/server';
app.get('/api/resource', async (req, res) => {
const token = req.headers.authorization?.replace('Bearer ', '') ?? '';
const result = await server.validateToken(token);
if (result.error) {
const error = new AuthrimServerError(
result.error.code as any,
result.error.message,
);
const headers = buildErrorHeaders(error, { realm: 'my-api' });
const body = buildErrorResponse(error);
return res.status(error.meta.httpStatus).set(headers).json(body);
}
res.json({ data: getResource(result.data.claims.sub) });
});
import { AuthrimServerError, buildErrorResponse } from '@authrim/server';
app.get('/api/resource', async (c) => {
const token = c.req.header('authorization')?.replace('Bearer ', '') ?? '';
const result = await server.validateToken(token);
if (result.error) {
const error = new AuthrimServerError(
result.error.code as any,
result.error.message,
);
return c.json(buildErrorResponse(error), error.meta.httpStatus);
}
return c.json({ data: getResource(result.data.claims.sub) });
});

Transient errors (network issues, JWKS fetch failures) can be retried. Use the meta.transient and meta.retryable flags to build retry logic:

import { AuthrimServerError } from '@authrim/server';
async function validateWithRetry(
server: AuthrimServer,
token: string,
maxRetries = 3,
): Promise<ValidatedToken> {
let lastError: AuthrimServerError | undefined;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const result = await server.validateToken(token);
if (!result.error) {
return result.data;
}
const error = new AuthrimServerError(
result.error.code as any,
result.error.message,
);
lastError = error;
// Only retry transient, retryable errors
if (!error.meta.retryable || attempt === maxRetries) {
throw error;
}
// Exponential backoff: 100ms, 200ms, 400ms
const delay = 100 * Math.pow(2, attempt);
await new Promise((resolve) => setTimeout(resolve, delay));
}
throw lastError;
}
CategoryTransientRetryableAction
JWT ValidationNoNoReturn 401 immediately
Issuer/AudienceNoNoReturn 401 immediately
DPoPNoNoReturn 401 immediately
JWKS FetchYesYesRetry with backoff
Operations (introspection/revocation)YesYesRetry with backoff
NetworkYesYesRetry with backoff
ConfigurationNoNoFix configuration and restart