@auth0/nextjs-auth0
Version:
Auth0 Next.js SDK
185 lines (184 loc) • 7.97 kB
TypeScript
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;
};