UNPKG

@auth0/nextjs-auth0

Version:
185 lines (184 loc) 7.97 kB
import { KeyObject } from "crypto"; import { DpopKeyPair, DpopOptions, RetryConfig } from "../types/dpop.js"; /** * Generates a new ES256 key pair for DPoP (Demonstrating Proof-of-Possession) operations. * * This function creates a cryptographically secure ES256 key pair suitable for DPoP proof * generation. The generated keys use the P-256 elliptic curve with SHA-256 hashing, * which is the required algorithm for DPoP as specified in RFC 9449. * * @returns Promise that resolves to a DpopKeyPair containing the private and public keys * * @example * ```typescript * import { generateDpopKeyPair } from "@auth0/nextjs-auth0/server"; * * const keyPair = await generateDpopKeyPair(); * * const auth0 = new Auth0Client({ * useDPoP: true, * dpopKeyPair: keyPair * }); * ``` * * @see {@link https://datatracker.ietf.org/doc/html/rfc9449 | RFC 9449: OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer (DPoP)} */ export declare function generateDpopKeyPair(): Promise<DpopKeyPair>; /** * Executes a function with retry logic for DPoP nonce errors. * * DPoP nonce errors occur when the authorization server requires a fresh nonce * for replay attack prevention. This function implements a single retry pattern * with configurable delay and jitter to handle these errors gracefully. * * The retry mechanism: * 1. If DPoP is not enabled, executes the function once without retry logic * 2. If DPoP is enabled: * - Executes the provided function * - If a DPoP nonce error occurs (400 with use_dpop_nonce error), waits with jitter * - Retries the function once * - The DPoP handle automatically learns and applies the new nonce on retry * 3. If the retry fails or any other error occurs, re-throws the error * * Note: The DPoP handle (oauth4webapi) is stateful and automatically learns nonces * from the DPoP-Nonce response header. No manual nonce injection is required. * * * ## Dual-Path Retry Logic * * The wrapper supports TWO different error paths, depending on how the caller * structures their token request: * * ### Path 1: HTTP Request Only (Recommended for Auth Code Flow) * **When to use:** Wrapping ONLY the HTTP request, not response processing* * **Error handling:** * - Nonce errors are detected via `response.status === 400` check (line 135) * - Non-nonce 400 errors pass through unchanged * - No exception is thrown; Response is returned for caller to process * * ### Path 2: HTTP Request + Response Processing (Used for Refresh/Connection Flows) * **When to use:** Wrapping both HTTP request AND response processing * * * **Error handling:** * - Nonce errors are detected via `isDPoPNonceError(error)` check (line 150) * - Non-nonce errors are re-thrown unchanged * - Caller receives either a successful response or an exception * * @template T - The return type of the function being executed * @param fn - The async function to execute with retry logic * @param config - Configuration object with retry behavior and DPoP enablement flag * @param config.isDPoPEnabled - Whether DPoP nonce retry logic should be applied (default: false) * @param config.delay - Retry delay in milliseconds (default: 100) * @param config.jitter - Whether to apply jitter to retry delay (default: true) * @returns The result of the function execution * @throws The original error if it's not a DPoP nonce error or if retry fails * * @example * ```typescript * import { withDPoPNonceRetry } from "@auth0/nextjs-auth0/server"; * import * as oauth from "oauth4webapi"; * * const dpopHandle = oauth.DPoP(client, keyPair); * * const result = await withDPoPNonceRetry( * async () => { * return await authorizationCodeGrantRequest( * metadata, * client, * clientAuth, * params, * redirectUri, * codeVerifier, * { DPoP: dpopHandle } * ); * }, * { isDPoPEnabled: true, delay: 100, jitter: true } * ); * * // The DPoP handle automatically learned the nonce from error response * // and injected it on retry * ``` * * @see {@link https://datatracker.ietf.org/doc/html/rfc9449#section-7.1 | RFC 9449 Section 7.1: DPoP Nonce} */ export declare function withDPoPNonceRetry<T>(fn: () => Promise<T>, config?: RetryConfig & { isDPoPEnabled?: boolean; }): Promise<T>; /** * Validates that a private and public key form a compatible key pair * by attempting to sign and verify a test message. * * This function ensures that the provided private and public keys are mathematically * compatible by performing a sign-and-verify operation with test data. This validation * helps catch mismatched key pairs, corrupted keys, or incorrect key formats early. * * @param privateKey - The private key as a Node.js KeyObject * @param publicKey - The public key as a Node.js KeyObject * @returns true if keys are compatible, false otherwise * * @example * ```typescript * import { createPrivateKey, createPublicKey } from "crypto"; * import { validateKeyPairCompatibility } from "@auth0/nextjs-auth0/server"; * * const privateKey = createPrivateKey(privateKeyPem); * const publicKey = createPublicKey(publicKeyPem); * * if (validateKeyPairCompatibility(privateKey, publicKey)) { * console.log("Keys are compatible"); * } else { * console.log("Keys are not compatible"); * } * ``` */ export declare function validateKeyPairCompatibility(privateKey: KeyObject, publicKey: KeyObject): boolean; /** * Configuration options for DPoP validation. * This interface mirrors the relevant options from Auth0ClientOptions. */ export interface DpopConfigurationOptions { useDPoP?: boolean; dpopKeyPair?: DpopKeyPair; dpopOptions?: DpopOptions; } /** * Validates DPoP configuration and returns keypair and options if available. * Attempts to load from environment variables if not provided in options. * * **Validation Behavior:** * - **Success**: Returns both `dpopKeyPair` and `dpopOptions` * - **Validation Failure**: Returns `{ dpopKeyPair: undefined, dpopOptions: undefined }` * * When DPoP cannot be properly configured, the system falls back to bearer authentication. * * **Performance Characteristics - Synchronous Key Loading:** * * Key loading and validation are performed synchronously during Auth0Client constructor execution. * * **Optimization Strategies:** * - **Recommended**: Pre-generate keys and pass via `dpopKeyPair` option to avoid env var parsing * - **Module-Level**: Instantiate Auth0Client at module level (lib/auth0.ts) for one-time cost * - **High-Throughput**: Consider pre-loading keys outside constructor for serverless environments * * @example Performance-optimized initialization * ```typescript * // Optimal: Pre-generated keys (no env var parsing) * import { generateKeyPair } from "oauth4webapi"; * const dpopKeyPair = await generateKeyPair("ES256"); * export const auth0 = new Auth0Client({ useDPoP: true, dpopKeyPair }); * ``` * * **Security-sensitive configuration:** * - `clockSkew`: Difference between client and server clocks (default: {@link DEFAULT_DPOP_CLOCK_SKEW}) * - `clockTolerance`: Acceptable time drift for DPoP proof validation (default: {@link DEFAULT_DPOP_CLOCK_TOLERANCE}, max recommended: {@link MAX_RECOMMENDED_DPOP_CLOCK_TOLERANCE}) * * Values exceeding {@link MAX_RECOMMENDED_DPOP_CLOCK_TOLERANCE} will trigger a warning but are not enforced. * Excessively large clock tolerance values may weaken DPoP security by allowing replay attacks within a * wider time window. Prefer synchronizing server clocks using NTP instead of increasing tolerance. * * @param options The configuration options containing DPoP settings * @returns Object containing DpopKeyPair and DpopOptions if validation succeeds, or both undefined if validation fails */ export declare function validateDpopConfiguration(options: DpopConfigurationOptions): { dpopKeyPair?: DpopKeyPair; dpopOptions?: DpopOptions; };