Skip to content

Token Validation

Overview

Token validation is the core function of @authrim/server. The validateToken() method performs a complete validation pipeline — parsing the JWT, fetching the signing key, verifying the signature, and checking all standard claims.

validateToken API

const result = await authrim.validateToken(token, options?);

Parameters

ParameterTypeDescription
tokenstringThe raw JWT access token (without the Bearer prefix)
optionsTokenValidationOptionsOptional validation options

TokenValidationOptions

interface TokenValidationOptions {
requiredScopes?: string[];
requiredClaims?: string[];
}
OptionTypeDescription
requiredScopesstring[]Scopes that must be present in the token’s scope claim
requiredClaimsstring[]Claim names that must exist in the token payload

Return Value: ValidatedToken

interface ValidatedToken {
claims: AccessTokenClaims;
token: string;
tokenType: 'Bearer' | 'DPoP';
expiresIn?: number;
}
FieldTypeDescription
claimsAccessTokenClaimsParsed and validated JWT claims
tokenstringThe original token string
tokenType'Bearer' | 'DPoP'Detected token type based on claims
expiresInnumber | undefinedSeconds until the token expires (calculated from exp)

AccessTokenClaims

interface AccessTokenClaims {
iss: string;
sub: string;
aud: string | string[];
exp: number;
iat: number;
nbf?: number;
jti?: string;
scope?: string;
client_id?: string;
cnf?: { jkt?: string };
[key: string]: unknown; // Custom claims
}

Validation Pipeline

The SDK validates tokens through the following pipeline. Each step must pass for the token to be accepted:

flowchart TD
    A["Receive JWT String"] --> B["Size Check
(max 8 KB)"] B --> C["Parse JWT
(Header + Payload + Signature)"] C --> D["Algorithm Check
(reject alg: none)"] D --> E["Fetch Signing Key
(JWKS by kid)"] E --> F["Verify Signature
(RS/PS/ES/EdDSA)"] F --> G["Validate iss
(timing-safe)"] G --> H["Validate aud
(timing-safe)"] H --> I["Validate exp
(+ clock tolerance)"] I --> J["Validate nbf
(+ clock tolerance)"] J --> K["Validate iat
(not in future)"] K --> L["Check Required Scopes"] L --> M["Check Required Claims"] M --> N["Detect Token Type
(Bearer vs DPoP)"] N --> O["Return ValidatedToken"]

If any step fails, the SDK throws a specific error. See the Error Types section below.

Supported Algorithms

The SDK supports the following JWS algorithms for signature verification:

AlgorithmKey TypeCurve / SizeDescription
RS256RSA2048+ bitsRSASSA-PKCS1-v1_5 with SHA-256
RS384RSA2048+ bitsRSASSA-PKCS1-v1_5 with SHA-384
RS512RSA2048+ bitsRSASSA-PKCS1-v1_5 with SHA-512
PS256RSA2048+ bitsRSASSA-PSS with SHA-256
PS384RSA2048+ bitsRSASSA-PSS with SHA-384
PS512RSA2048+ bitsRSASSA-PSS with SHA-512
ES256ECP-256ECDSA with SHA-256
ES384ECP-384ECDSA with SHA-384
ES512ECP-521ECDSA with SHA-512
EdDSAOKPEd25519Edwards-curve Digital Signature

Claims Validation Details

Issuer (iss)

The iss claim must exactly match one of the configured issuer values. Comparison uses a timing-safe algorithm to prevent side-channel attacks.

// Single issuer
const authrim = new AuthrimServer({
issuer: 'https://auth.example.com',
audience: 'https://api.example.com',
});
// Multiple issuers
const authrim = new AuthrimServer({
issuer: [
'https://auth.example.com',
'https://auth.partner.com',
],
audience: 'https://api.example.com',
});

Audience (aud)

The aud claim must contain at least one of the configured audience values. The aud claim can be a single string or an array of strings. All comparisons are timing-safe.

// Token with aud: "https://api.example.com" — matches
// Token with aud: ["https://api.example.com", "other"] — matches
// Token with aud: "https://other.example.com" — rejected

Expiration (exp)

The exp claim is checked against the current time plus clockToleranceSeconds:

token is valid if: exp + clockToleranceSeconds > now

With the default tolerance of 60 seconds, a token that expired up to 60 seconds ago is still accepted.

Not Before (nbf)

If present, the nbf claim is checked against the current time minus clockToleranceSeconds:

token is valid if: nbf - clockToleranceSeconds <= now

Issued At (iat)

The iat claim is sanity-checked to ensure the token was not issued in the future (with clock tolerance):

token is valid if: iat - clockToleranceSeconds <= now

Scope Validation

Enforce required scopes using the requiredScopes option:

// Require specific scopes
const result = await authrim.validateToken(token, {
requiredScopes: ['read:users', 'write:users'],
});

The SDK checks that all specified scopes are present in the token’s scope claim (space-delimited string). If any required scope is missing, an InsufficientScopeError is thrown.

Bearer vs DPoP Auto-Detection

The SDK automatically detects the token type based on the cnf (confirmation) claim:

  • If the token contains cnf.jkt (JWK Thumbprint), the token is classified as DPoP
  • Otherwise, the token is classified as Bearer
const result = await authrim.validateToken(token);
if (result.tokenType === 'DPoP') {
// This token is sender-constrained — validate the DPoP proof too
await authrim.validateDPoP(dpopProof, {
accessTokenHash: computeHash(token),
expectedThumbprint: result.claims.cnf!.jkt!,
method: 'GET',
url: 'https://api.example.com/resource',
});
}

For full DPoP validation, see DPoP Validation.

Code Examples

Basic Token Validation

import { AuthrimServer } from '@authrim/server';
const authrim = new AuthrimServer({
issuer: 'https://auth.example.com',
audience: 'https://api.example.com',
});
await authrim.init();
try {
const result = await authrim.validateToken(accessToken);
console.log('User:', result.claims.sub);
console.log('Scopes:', result.claims.scope);
console.log('Expires in:', result.expiresIn, 'seconds');
} catch (error) {
console.error('Token validation failed:', error.message);
}

With Required Scopes

try {
const result = await authrim.validateToken(accessToken, {
requiredScopes: ['read:orders', 'write:orders'],
});
// Token is valid and has both scopes
} catch (error) {
if (error.name === 'InsufficientScopeError') {
// Token is valid but missing required scopes — return 403
return res.status(403).json({ error: 'insufficient_scope' });
}
// Token is invalid — return 401
return res.status(401).json({ error: 'invalid_token' });
}

Accessing Custom Claims

Tokens may contain custom claims added by the authorization server (e.g., roles, tenant ID). Access them through the claims object:

const result = await authrim.validateToken(accessToken);
// Access custom claims with type assertion
const roles = result.claims['roles'] as string[];
const tenantId = result.claims['tenant_id'] as string;
// Or use a type parameter for full type safety
interface MyTokenClaims extends AccessTokenClaims {
roles: string[];
tenant_id: string;
}
const claims = result.claims as MyTokenClaims;
console.log(claims.roles); // ['admin', 'user']
console.log(claims.tenant_id); // 'tenant-123'

Error Types

ErrorDescriptionHTTP Status
InsecureAlgorithmErrorToken uses alg: none or an unsupported algorithm401
InvalidSignatureErrorSignature verification failed401
TokenExpiredErrorToken exp claim has passed (beyond clock tolerance)401
TokenNotYetValidErrorToken nbf claim is in the future (beyond clock tolerance)401
InvalidIssuerErrorToken iss does not match configured issuer(s)401
InvalidAudienceErrorToken aud does not match configured audience(s)401
InsufficientScopeErrorToken is missing one or more required scopes403
MissingClaimErrorToken is missing a required claim401
JwksErrorFailed to fetch or process JWKS500
TokenSizeLimitErrorToken exceeds 8 KB size limit401

Next Steps