OAuth 2.0 and OpenID Connect client library for JavaScript runtimes with comprehensive authentication flows and security features.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
FAPI compliance, DPoP, JARM, JAR, PAR, response encryption, and other advanced security mechanisms for high-security OAuth 2.0 and OpenID Connect implementations.
Implement sender-constrained access tokens using DPoP for enhanced security.
/**
* Generate DPoP key pair
* @param alg - JWS algorithm (default: 'ES256')
* @param options - Key generation options
* @returns Promise resolving to cryptographic key pair
*/
function randomDPoPKeyPair(
alg?: string,
options?: GenerateKeyPairOptions
): Promise<CryptoKeyPair>;
/**
* Create DPoP handle for sender-constrained tokens
* @param config - Configuration instance
* @param keyPair - DPoP key pair for signing proofs
* @param options - JWT modification options
* @returns DPoP handle for use with requests
*/
function getDPoPHandle(
config: Configuration,
keyPair: CryptoKeyPair,
options?: ModifyAssertionOptions
): DPoPHandle;Usage Examples:
import * as client from "openid-client";
// Generate DPoP key pair
const dpopKeyPair = await client.randomDPoPKeyPair("ES256");
// Create DPoP handle
const dpopHandle = client.getDPoPHandle(config, dpopKeyPair);
// Use DPoP with authorization code grant
const tokens = await client.authorizationCodeGrant(
config,
currentUrl,
{ pkceCodeVerifier: codeVerifier },
undefined,
{ DPoP: dpopHandle }
);
// Check if access token is sender-constrained
if (tokens.token_type === "dpop") {
console.log("Access token is sender-constrained with DPoP");
}
// Use DPoP handle with protected resource requests
const response = await client.fetchProtectedResource(
config,
tokens.access_token,
new URL("https://api.example.com/data"),
"GET",
undefined,
undefined,
{ DPoP: dpopHandle }
);
// DPoP works with all token operations
const newTokens = await client.refreshTokenGrant(
config,
tokens.refresh_token,
undefined,
{ DPoP: dpopHandle }
);Enable JWT-secured authorization responses for enhanced security.
/**
* Enable JWT Authorization Response Mode
* @param config - Configuration instance to modify
*/
function useJwtResponseMode(config: Configuration): void;Usage Examples:
import * as client from "openid-client";
// Enable JARM during discovery
const config = await client.discovery(
new URL("https://example.com"),
"client-id",
"client-secret",
undefined,
{
execute: [client.useJwtResponseMode]
}
);
// Or enable on existing configuration
client.useJwtResponseMode(config);
// Authorization responses will now be JWT-signed
const authUrl = client.buildAuthorizationUrl(config, {
redirect_uri: "https://example.com/callback",
scope: "openid profile",
response_mode: "jwt", // This is set automatically
code_challenge: codeChallenge,
code_challenge_method: "S256"
});
// Handle JWT response (library validates signature automatically)
const tokens = await client.authorizationCodeGrant(
config,
currentUrl, // Contains JWT response
{ pkceCodeVerifier: codeVerifier }
);Enable code id_token response type for hybrid flow with ID Token validation.
/**
* Enable OpenID Connect hybrid flow (code id_token response type)
* @param config - Configuration instance to modify
*/
function useCodeIdTokenResponseType(config: Configuration): void;Usage Examples:
import * as client from "openid-client";
// Enable hybrid flow
const config = await client.discovery(
new URL("https://example.com"),
"client-id",
"client-secret",
undefined,
{
execute: [client.useCodeIdTokenResponseType]
}
);
// Build authorization URL (response_type=code id_token is set automatically)
const authUrl = client.buildAuthorizationUrl(config, {
redirect_uri: "https://example.com/callback",
scope: "openid profile email",
nonce: client.randomNonce(),
state: client.randomState(),
code_challenge: codeChallenge,
code_challenge_method: "S256"
});
// Handle hybrid flow response
const tokens = await client.authorizationCodeGrant(
config,
currentUrl, // Contains both code and id_token
{
pkceCodeVerifier: codeVerifier,
expectedNonce: expectedNonce,
expectedState: expectedState
}
);
// Access both authorization response ID Token and token endpoint tokens
console.log("Auth response ID Token:", tokens.claims());
console.log("Token endpoint access token:", tokens.access_token);Enable ID Token-only implicit flow for specific use cases.
/**
* Enable OpenID Connect implicit flow (id_token response type)
* @param config - Configuration instance to modify
*/
function useIdTokenResponseType(config: Configuration): void;Usage Examples:
import * as client from "openid-client";
// Enable implicit flow (typically for public clients)
const config = await client.discovery(
new URL("https://example.com"),
"public-client-id",
undefined,
client.None(), // Public client authentication
{
execute: [client.useIdTokenResponseType]
}
);
// Build authorization URL for implicit flow
const authUrl = client.buildAuthorizationUrl(config, {
redirect_uri: "https://spa.example.com/callback",
scope: "openid profile email",
nonce: client.randomNonce(),
state: client.randomState()
// response_type=id_token is set automatically
});
// Handle implicit flow response (use implicitAuthentication, not authorizationCodeGrant)
const idTokenClaims = await client.implicitAuthentication(
config,
new URL(location.href), // Browser location with hash fragment
expectedNonce,
{
expectedState: expectedState,
maxAge: 3600
}
);
console.log("User authenticated:", idTokenClaims.sub);
console.log("Email:", idTokenClaims.email);Enable FAPI 1.0 Advanced profile with detached signature response checks.
/**
* Enable FAPI 1.0 Advanced detached signature response checks
* @param config - Configuration instance to modify
*/
function enableDetachedSignatureResponseChecks(config: Configuration): void;Usage Examples:
import * as client from "openid-client";
// Enable FAPI 1.0 Advanced profile
const config = await client.discovery(
new URL("https://fapi-server.example.com"),
"client-id",
"client-secret",
undefined,
{
execute: [
client.useCodeIdTokenResponseType, // Required for FAPI Advanced
client.enableDetachedSignatureResponseChecks // Enable FAPI validation
]
}
);
// FAPI requires specific security measures
const authUrl = client.buildAuthorizationUrl(config, {
redirect_uri: "https://example.com/callback",
scope: "openid accounts",
nonce: client.randomNonce(),
state: client.randomState(),
code_challenge: codeChallenge,
code_challenge_method: "S256" // PKCE required for FAPI
});
// Handle FAPI response with enhanced validation
const tokens = await client.authorizationCodeGrant(
config,
currentUrl,
{
pkceCodeVerifier: codeVerifier,
expectedNonce: expectedNonce,
expectedState: expectedState
}
);Enable JWS signature validation for enhanced security.
/**
* Enable non-repudiation checks (JWS signature validation)
* @param config - Configuration instance to modify
*/
function enableNonRepudiationChecks(config: Configuration): void;Usage Examples:
import * as client from "openid-client";
// Enable signature validation
const config = await client.discovery(
new URL("https://example.com"),
"client-id",
"client-secret",
undefined,
{
execute: [client.enableNonRepudiationChecks]
}
);
// All JWT responses (UserInfo, Introspection, etc.) will be signature-validated
const userInfo = await client.fetchUserInfo(
config,
accessToken,
expectedSubject
);
// JWT introspection responses are validated
const introspection = await client.tokenIntrospection(config, token);Enable JWE decryption for encrypted responses.
/**
* Enable decryption of encrypted responses
* @param config - Configuration instance to modify
* @param contentEncryptionAlgorithms - Allowed content encryption algorithms
* @param keys - Decryption keys
*/
function enableDecryptingResponses(
config: Configuration,
contentEncryptionAlgorithms?: string[],
...keys: Array<CryptoKey | DecryptionKey>
): void;Usage Examples:
import * as client from "openid-client";
// Generate or import decryption key
const keyPair = await crypto.subtle.generateKey(
{ name: "ECDH", namedCurve: "P-256" },
true,
["deriveKey"]
);
// Enable response decryption
client.enableDecryptingResponses(
config,
["A256GCM", "A128CBC-HS256"], // Allowed content encryption algorithms
keyPair.privateKey
);
// Or with DecryptionKey interface for more control
const decryptionKey: client.DecryptionKey = {
key: keyPair.privateKey,
alg: "ECDH-ES+A256KW", // Key management algorithm
kid: "my-decryption-key-id"
};
client.enableDecryptingResponses(
config,
["A256GCM"],
decryptionKey
);
// Encrypted responses will be automatically decrypted
const userInfo = await client.fetchUserInfo(config, accessToken, expectedSubject);
const introspection = await client.tokenIntrospection(config, token);Manage JWKS cache for cloud functions and stateless environments.
/**
* Export JWKS cache for external storage
* @param config - Configuration instance
* @returns Exported JWKS cache or undefined
*/
function getJwksCache(config: Configuration): ExportedJWKSCache | undefined;
/**
* Import JWKS cache from external storage
* @param config - Configuration instance
* @param jwksCache - Previously exported JWKS cache
*/
function setJwksCache(config: Configuration, jwksCache: ExportedJWKSCache): void;Usage Examples:
import * as client from "openid-client";
// In a serverless function
export async function handler(event: any) {
const config = await client.discovery(
new URL("https://example.com"),
"client-id",
"client-secret"
);
// Load JWKS cache from external storage (Redis, DynamoDB, etc.)
const savedCache = await loadJwksCacheFromStorage();
if (savedCache) {
client.setJwksCache(config, savedCache);
}
// Perform operations that may use JWKS
const userInfo = await client.fetchUserInfo(config, accessToken, expectedSubject);
// Save updated JWKS cache
const currentCache = client.getJwksCache(config);
if (currentCache) {
await saveJwksCacheToStorage(currentCache);
}
return { statusCode: 200, body: JSON.stringify(userInfo) };
}
// Cache storage implementation example
async function loadJwksCacheFromStorage(): Promise<client.ExportedJWKSCache | null> {
try {
const cached = await redis.get("jwks-cache");
return cached ? JSON.parse(cached) : null;
} catch {
return null;
}
}
async function saveJwksCacheToStorage(cache: client.ExportedJWKSCache): Promise<void> {
try {
await redis.setex("jwks-cache", 3600, JSON.stringify(cache)); // 1 hour TTL
} catch (error) {
console.warn("Failed to save JWKS cache:", error);
}
}Override default security settings for development or specific requirements.
/**
* Allow insecure HTTP requests (development only)
* @param config - Configuration instance to modify
*/
function allowInsecureRequests(config: Configuration): void;Usage Examples:
import * as client from "openid-client";
// For development/testing only - allow HTTP
const config = await client.discovery(
new URL("http://localhost:8080"), // HTTP issuer
"dev-client-id",
"dev-client-secret",
undefined,
{
execute: [client.allowInsecureRequests]
}
);
// Or enable on existing configuration
client.allowInsecureRequests(config);Re-exported error classes from oauth4webapi for comprehensive error handling:
/**
* Authorization endpoint error response
*/
class AuthorizationResponseError extends Error {
error: string;
error_description?: string;
error_uri?: string;
state?: string;
}
/**
* HTTP response body parsing error
*/
class ResponseBodyError extends Error {
response: Response;
}
/**
* WWW-Authenticate header parsing error
*/
class WWWAuthenticateChallengeError extends Error {
challenges: WWWAuthenticateChallenge[];
}interface DecryptionKey {
/** Decryption private key */
key: CryptoKey;
/** JWE Key Management Algorithm identifier */
alg?: string;
/** Key ID */
kid?: string;
}
interface DPoPHandle {
// DPoP handle implementation (opaque to consumers)
}
interface GenerateKeyPairOptions {
/** Whether the key should be extractable */
extractable?: boolean;
}
interface ExportedJWKSCache {
/** Cached JWKS data */
jwks: any;
/** Cache timestamp */
uat: number;
}
interface WWWAuthenticateChallenge {
scheme: string;
parameters: WWWAuthenticateChallengeParameters;
}
interface WWWAuthenticateChallengeParameters {
[parameter: string]: string;
}/**
* Symbol for JWT modification in assertions
*/
declare const modifyAssertion: unique symbol;
/**
* Symbol for clock skew adjustment
*/
declare const clockSkew: unique symbol;
/**
* Symbol for clock tolerance configuration
*/
declare const clockTolerance: unique symbol;Symbol Usage Examples:
import * as client from "openid-client";
// Clock management for JWT validation
const clientMetadata: client.ClientMetadata = {
client_id: "client-id",
client_secret: "client-secret",
[client.clockSkew]: 30, // Local clock is 30 seconds behind server
[client.clockTolerance]: 60 // Allow 60 seconds tolerance for JWT validation
};
const config = new client.Configuration(
serverMetadata,
"client-id",
clientMetadata
);
// JWT assertion modification
const privateKeyJwtAuth = client.PrivateKeyJwt(privateKey, {
[client.modifyAssertion]: (header, payload) => {
// Modify JWT before signing
header.kid = "my-key-id";
payload.jti = crypto.randomUUID();
payload.iat = Math.floor(Date.now() / 1000) - 30; // 30 seconds ago
}
});
// Security override symbols (use with caution)
const tokens = await client.authorizationCodeGrant(
config,
currentUrl,
{
expectedState: client.skipStateCheck, // Skip state validation (not recommended)
expectedNonce: expectedNonce
}
);
const userInfo = await client.fetchUserInfo(
config,
tokens.access_token,
client.skipSubjectCheck // Skip subject validation (not recommended)
);Install with Tessl CLI
npx tessl i tessl/npm-openid-client