oauth-fetch
Version:
A lightweight HTTP client built on top of the native fetch API, designed to simplify making requests to both public and OAuth-protected resources (Bearer and DPoP).
603 lines (593 loc) • 22.4 kB
text/typescript
/**
* HTTP content type constants, representing common content types used in requests and responses.
*/
declare const HTTP_CONTENT_TYPE: {
readonly JSON: "json";
readonly TEXT: "text";
readonly FORM_DATA: "formData";
readonly FORM_URL_ENCODED: "formUrlEncoded";
};
/**
* Constants for HTTP methods (GET, POST, PATCH, PUT, DELETE).
*/
declare const HTTP_METHOD: {
readonly GET: "GET";
readonly POST: "POST";
readonly PATCH: "PATCH";
readonly PUT: "PUT";
readonly DELETE: "DELETE";
};
/**
* Supported OAuth token types, including case-insensitive variations for each type.
*/
declare const SUPPORTED_TOKEN_TYPES: {
readonly BEARER: "Bearer";
readonly DPOP: "DPoP";
};
/**
* Supported cryptographic algorithms for DPoP, along with their valid parameters.
* - ECDSA: P-256, P-384, P-521 curves
* - RSA-PSS: 2048, 3072, 4096 bit modulus lengths
* - EdDSA: Ed25519 curve
*/
declare const DPOP_SUPPORTED_ALGORITHMS: {
readonly ECDSA: readonly ["P-256", "P-384", "P-521"];
readonly "RSA-PSS": readonly ["2048", "3072", "4096"];
readonly EdDSA: readonly ["Ed25519"];
};
type TokenProviderTokenType = (typeof SUPPORTED_TOKEN_TYPES)[keyof typeof SUPPORTED_TOKEN_TYPES];
/**
* Represents the response from a token acquisition operation.
* Contains the access token and its type for use in API requests.
*/
type TokenProviderGetTokenResponse = {
/**
* The OAuth access token value used for API authorization.
* This token should be included in the Authorization header of API requests.
*/
access_token: string;
/**
* The type of token returned, which determines how it should be used in requests.
* Common types include "Bearer" and "DPoP" (Demonstrating Proof-of-Possession).
*/
token_type: TokenProviderTokenType;
};
/**
* Abstract class representing a Token Provider responsible for managing the
* lifecycle of access tokens.
*
* This class defines the contract for acquiring, refreshing, and caching tokens.
* By extending this class, you can implement custom strategies for interacting with identity
* providers such as Auth0, Clerk, WorkOS, or any other OAuth-compliant service.
*
* It also provides a mechanism to override the configuration for token
* acquisition per request, enabling granular control over authorization
* parameters.
*
* @example
* ```typescript
* import { AbstractTokenProvider, type TokenProviderGetTokenResponse } from "oauth-fetch";
* import { Auth0Client, GetTokenSilentlyOptions } from "@auth0/auth0-spa-js";
*
* export class Auth0TokenProvider extends AbstractTokenProvider<GetTokenSilentlyOptions> {
* private auth0: Auth0Client;
*
* constructor(auth0: Auth0Client, config?: GetTokenSilentlyOptions) {
* super(config);
* this.auth0 = auth0;
* }
*
* async getToken(): Promise<TokenProviderGetTokenResponse> {
* try {
* const accessToken = await this.auth0.getTokenSilently(this.config);
* return {
* access_token: accessToken,
* token_type: "Bearer",
* };
* } catch {
* throw new Error("Failed to retrieve access token.");
* }
* }
* }
* ```
*/
declare abstract class AbstractTokenProvider<TokenProviderGetTokenConfig = unknown> {
/**
* Allows any property to be dynamically attached to the instance.
*/
[key: string]: unknown;
/**
* Configuration for the token provider's `getToken()` method.
* This can include options for token acquisition, caching, and other settings
* that the provider supports.
*/
protected config?: TokenProviderGetTokenConfig;
/**
* Initializes a new instance of the `AbstractTokenProvider`.
*/
constructor(config?: TokenProviderGetTokenConfig);
/**
* Retrieves a valid OAuth access token for API requests.
*
* This method should be responsible for the entire token lifecycle management:
* - Returning cached tokens if they're still valid
* - Refreshing expired tokens automatically when possible
* - Handling token acquisition when no valid token is available
* - Implementing appropriate error handling for token-related failures
*
* Implementations should be designed to minimize overhead by efficiently
* caching tokens and only performing network requests when necessary.
*
* @throws {TokenProviderError} If `access_token` is not returned
* @throws {TokenProviderError} If `token_type` is not returned
* @throws {TokenProviderError} If `token_type` is not supported
*/
abstract getToken(): Promise<TokenProviderGetTokenResponse>;
/**
* Creates a new instance of the token provider with overridden `getToken` config.
*
* This method allows you to generate a modified instance of the token provider
* with specific configuration changes. Only the matching properties are overridden,
* while the rest of the configuration remains unchanged. This is ideal for fine-tuning
* authorization scopes or parameters on a per-request basis without affecting the
* original instance.
*
* @example
* ```typescript
* const newTokenProvider = tokenProvider.withConfigOverrides({
* authorizationParams: {
* scope: "write:profile",
* },
* });
* ```
*/
withConfigOverrides(overrides: Partial<TokenProviderGetTokenConfig>): this;
}
/**
* Type representing the available HTTP content types defined in the `HTTP_CONTENT_TYPE` constant.
* It includes values like 'json', 'text', 'formData', and 'formUrlEncoded'.
*/
type HttpContentType = (typeof HTTP_CONTENT_TYPE)[keyof typeof HTTP_CONTENT_TYPE];
/**
* Type representing the available HTTP methods defined in the `HTTP_METHOD` constant.
* It includes methods like 'GET', 'POST', 'PATCH', 'PUT', and 'DELETE'.
*/
type HttpMethod = (typeof HTTP_METHOD)[keyof typeof HTTP_METHOD];
/**
* Cryptographic key pair used for generating and verifying DPoP token proof-of-possession.
*/
type DPoPKeyPair = {
/** The private key used for signing DPoP proofs */
privateKey: CryptoKey;
/** The public key used for generating the JWK thumbprint and verifying DPoP proofs */
publicKey: CryptoKey;
};
/**
* Enum-like type representing the supported cryptographic algorithms for DPoP proof generation.
* This type is derived from the keys of `DPOP_SUPPORTED_ALGORITHMS`.
*/
type DPoPSupportedAlgorithms = keyof typeof DPOP_SUPPORTED_ALGORITHMS;
/**
* Supported curves (for ECDSA/EdDSA) or modulus lengths (for RSA) that are valid for the specified algorithm.
* This type is based on the values associated with each algorithm in `DPOP_SUPPORTED_ALGORITHMS`.
*/
type DPoPSupportedCurveOrModulus = (typeof DPOP_SUPPORTED_ALGORITHMS)[DPoPSupportedAlgorithms][number];
/**
* Configuration options for generating a DPoP key pair.
*/
type DPoPKeyGenConfig = {
/**
* The cryptographic algorithm to use for generating the DPoP key pair.
* @default "ECDSA"
*/
algorithm?: DPoPSupportedAlgorithms;
/**
* The curve (for ECDSA/EdDSA) or modulus length (for RSA) to use.
* @default "P-256" for ECDSA
*/
curveOrModulus?: DPoPSupportedCurveOrModulus;
};
/**
* Configuration parameters required to generate a DPoP proof.
*/
type DPoPGenerateProofConfig = {
/** The target URL for the HTTP request to which the proof is bound */
url: URL;
/** The HTTP method (e.g., GET, POST, PUT) used for the request */
method: HttpMethod;
/** The DPoP key pair used for signing the proof */
dpopKeyPair: DPoPKeyPair;
/**
* An optional nonce provided by the server for replay protection.
* If a DPoP-Nonce header was returned in a previous response, include it here.
*/
nonce?: string;
/**
* An optional access token to include with the proof.
* This is used when the proof is tied to an access token in the Authorization: DPoP header.
*/
accessToken?: string;
};
/**
* Configuration for public (non-authenticated) resources
*/
type OAuthFetchPublicResourceConfig = {
/** Base URL for API requests (e.g., 'https://api.example.com') */
baseUrl: string;
/**
* Content type for requests (defaults to JSON if not specified)
* @default "json"
*/
contentType?: HttpContentType;
/**
* Custom fetch implementation
*
* @example
* // Example using a custom fetch implementation
* const client = new OAuthFetch({
* baseUrl: 'https://api.example.com',
* isProtected: false,
* customFetch: async (url, options) => {
* // Custom fetch logic
* }
* });
*/
customFetch?: typeof fetch;
/** Whether the API requires authentication (defaults to true)
* @default true
*/
isProtected: false;
};
/**
* Configuration for protected (authenticated) resources
*/
type OAuthFetchPrivateResourceConfig = {
/** Base URL for API requests (e.g., 'https://api.example.com') */
baseUrl: string;
/**
* Content type for requests (defaults to JSON if not specified)
* @default "json"
*/
contentType?: HttpContentType;
/**
* Custom fetch implementation
*
* @example
* // Example using a custom fetch implementation
* const client = new OAuthFetch({
* baseUrl: 'https://api.example.com',
* isProtected: false,
* customFetch: async (url, options) => {
* // Custom fetch logic
* }
* });
*/
customFetch?: typeof fetch;
/** Whether the API requires authentication (defaults to true)
* @default true
*/
isProtected?: true;
/** Provider responsible for fetching OAuth tokens */
tokenProvider: AbstractTokenProvider;
/** Required for DPoP authentication flow */
dpopKeyPair?: DPoPKeyPair;
};
/**
* Extended request options that support authentication overrides and all standard fetch options
*/
type RequestOptions = Omit<RequestInit, "method" | "body"> & {
/** Override the default protection setting for this specific request */
isProtected?: boolean;
/** Override the default provider responsible for fetching OAuth tokens */
tokenProvider?: AbstractTokenProvider;
};
type RequestBody = Record<string, unknown> | string;
/**
* OAuth-compatible HTTP client that supports Bearer and DPoP tokens for secure API requests.
*
* This client can be configured globally for an API with default authentication settings,
* but also supports per-request overrides for accessing resources with different token
* scopes, audiences, or authentication requirements.
*
* All HTTP methods accept standard fetch options (cache, mode, credentials, signal, etc.)
* in addition to the custom authentication options.
*
* @example
* // Public API client
* const publicClient = new OAuthFetch({
* baseUrl: 'https://api.example.com',
* isProtected: false
* });
*
* // Protected API with Bearer tokens
* const bearerClient = new OAuthFetch({
* baseUrl: 'https://api.example.com',
* tokenProvider: new MyTokenProvider()
* });
*
* // Protected API with DPoP tokens
* const dpopClient = new OAuthFetch({
* baseUrl: 'https://api.example.com',
* tokenProvider: new MyDPoPTokenProvider(),
* dpopKeyPair: await createDPoPKeyPair()
* });
*/
declare class OAuthFetch {
#private;
/**
* Initializes a new `OAuthFetch` instance with the provided configuration.
*
* @throws {ConfigurationError} If `isProtected` is `true` and `tokenProvider` is not provided
*/
constructor(config: OAuthFetchPrivateResourceConfig | OAuthFetchPublicResourceConfig);
/**
* Makes an HTTP GET request.
*
* @throws {ApiResponseError} If API responds with a non-successful status code
* @throws {ConfigurationError} If `isProtected` is `true` and `tokenProvider` is missing
* @throws {ConfigurationError} If token provider returns a DPoP token type, and `dpopKeyPair` is missing
*/
get(endpoint: string, options?: RequestOptions): Promise<unknown>;
/**
* Makes an HTTP DELETE request.
*
* @throws {ApiResponseError} If API responds with a non-successful status code
* @throws {ConfigurationError} If `isProtected` is `true` and `tokenProvider` is missing
* @throws {ConfigurationError} If token provider returns a DPoP token type, and `dpopKeyPair` is missing
*/
delete(endpoint: string, body?: RequestBody, options?: RequestOptions): Promise<unknown>;
/**
* Makes an HTTP POST request.
*
* @throws {ApiResponseError} If API responds with a non-successful status code
* @throws {ConfigurationError} If `isProtected` is `true` and `tokenProvider` is missing
* @throws {ConfigurationError} If token provider returns a DPoP token type, and `dpopKeyPair` is missing
*/
post(endpoint: string, body?: RequestBody, options?: RequestOptions): Promise<unknown>;
/**
* Makes an HTTP PATCH request.
*
* @throws {ApiResponseError} If API responds with a non-successful status code
* @throws {ConfigurationError} If `isProtected` is `true` and `tokenProvider` is missing
* @throws {ConfigurationError} If token provider returns a DPoP token type, and `dpopKeyPair` is missing
*/
patch(endpoint: string, body?: RequestBody, options?: RequestOptions): Promise<unknown>;
/**
* Makes an HTTP PUT request.
*
* @throws {ApiResponseError} If API responds with a non-successful status code
* @throws {ConfigurationError} If `isProtected` is `true` and `tokenProvider` is missing
* @throws {ConfigurationError} If token provider returns a DPoP token type, and `dpopKeyPair` is missing
*/
put(endpoint: string, body?: RequestBody, options?: RequestOptions): Promise<unknown>;
}
/**
* Utility class for DPoP (Demonstrating Proof-of-Possession) operations.
*
* DPoP is a security mechanism used in OAuth 2.0 that cryptographically binds access tokens
* to a specific client by requiring the client to prove possession of a private key.
* This prevents token theft and misuse, as the stolen token cannot be used without the
* corresponding private key.
*
* This class provides methods to:
* - Generate DPoP key pairs for signing proofs
* - Calculate JWK thumbprints for authorization requests
* - Generate DPoP proofs for HTTP requests
*
* @example
* ```typescript
* // Basic usage flow:
*
* // 1. Generate a DPoP key pair (once per client session)
* const keyPair = await DPoPUtils.generateKeyPair();
*
* // 2. For authorization requests, calculate JWK thumbprint
* const jkt = await DPoPUtils.calculateJwkThumbprint(keyPair.publicKey);
*
* // 3. Generate a DPoP proof for an API request
* const proof = await DPoPUtils.generateProof({
* url: new URL("https://api.example.com/resources"),
* method: "GET",
* dpopKeyPair: keyPair,
* accessToken: "eyJhbGciOiJSUzI1NiIsI..." // Optional
* });
*
* // 4. Use the proof in an HTTP request
* fetch("https://api.example.com/resources", {
* headers: {
* "DPoP": proof,
* "Authorization": `DPoP ${accessToken}`
* }
* });
* ```
*/
declare class DPoPUtils {
#private;
private constructor();
/**
* Calculates the DPoP JWK Thumbprint (dpop_jkt) for a public key.
*
* The JWK Thumbprint is a base64url-encoded SHA-256 hash of the JSON representation
* of the JWK, with specific formatting requirements as per RFC 7638.
* This can be used to bind an authorization code to a DPoP key pair by including
* it as the `dpop_jkt` parameter in authorization requests.
*
* @throws {ConfigurationError} If the public key uses an unsupported algorithm.
*
* @example
* ```typescript
* // Generate a DPoP key pair
* const keyPair = await DPoPUtils.generateKeyPair();
*
* // Calculate the JWK thumbprint (dpop_jkt)
* const jkt = await DPoPUtils.calculateJwkThumbprint(keyPair.publicKey);
*
* // Use the JWK thumbprint in an authorization request
* const authUrl = new URL("https://auth.example.com/oauth/authorize");
* authUrl.searchParams.set("client_id", "client123");
* authUrl.searchParams.set("response_type", "code");
* authUrl.searchParams.set("redirect_uri", "https://app.example.com/callback");
* authUrl.searchParams.set("dpop_jkt", jkt);
* ```
*/
static calculateJwkThumbprint(publicKey: CryptoKey): Promise<string>;
/**
* Generates a new DPoP key pair using the specified cryptographic algorithm and parameters.
*
* This method creates a cryptographic key pair that can be used for signing DPoP proofs.
* The generated keys are non-extractable and can only be used for signing and verification.
* Typically, you should generate a key pair once per client session and reuse it
* for all DPoP proofs in that session.
*
* @throws {ConfigurationError} If the requested algorithm/curve combination is not supported
*
* @example
* ```typescript
* // Generate a key pair using default parameters (ECDSA with P-256 curve)
* const defaultKeyPair = await DPoPUtils.generateKeyPair();
*
* // Generate an ECDSA key pair with the P-384 curve
* const ecdsaKeyPair = await DPoPUtils.generateKeyPair({
* algorithm: "ECDSA",
* curveOrModulus: "P-384"
* });
*
* // Generate an RSA key pair with 2048-bit modulus
* const rsaKeyPair = await DPoPUtils.generateKeyPair({
* algorithm: "RSA-PSS",
* curveOrModulus: "2048"
* });
*
* // Generate an EdDSA key pair with Ed25519 curve
* const eddsaKeyPair = await DPoPUtils.generateKeyPair({
* algorithm: "EdDSA",
* curveOrModulus: "Ed25519"
* });
* ```
*/
static generateKeyPair({ algorithm, curveOrModulus, }?: DPoPKeyGenConfig): Promise<DPoPKeyPair>;
/**
* Generates a DPoP proof JWT for a specific HTTP request.
*
* The proof is a JWT that binds the request to the DPoP key pair and optionally to an access token.
* It proves possession of the private key corresponding to the public key included in the JWT header.
* Each proof has a unique JTI (JWT ID) and a timestamp to prevent replay attacks.
*
* @throws {ConfigurationError} If the DPoP key pair is not properly initialized
* @throws {ConfigurationError} If the private key's extractable value is `true`
* @throws {ConfigurationError} If the private key does not have the `sign` usage permission.
* @throws {ConfigurationError} If the public key uses an unsupported algorithm.
*
* @example
* ```typescript
* // Generate a DPoP proof for a request without an access token
* const basicProof = await DPoPUtils.generateProof({
* url: new URL("https://auth.example.com/oauth/token"),
* method: "POST",
* dpopKeyPair: keyPair
* });
*
* // Use the proof in a request header
* fetch("https://auth.example.com/oauth/token", {
* method: "POST",
* headers: {
* "DPoP": basicProof
* }
* });
*
* // Generate a DPoP proof bound to an access token
* const tokenProof = await DPoPUtils.generateProof({
* url: new URL("https://api.example.com/protected"),
* method: "GET",
* dpopKeyPair: keyPair,
* accessToken: "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp...",
* nonce: "8IBTHwOdqNK..." // From previous DPoP-Nonce header
* });
*
* // Use the proof with the access token to consume a protected API
* fetch("https://api.example.com/protected", {
* method: "GET",
* headers: {
* "DPoP": tokenProof,
* "Authorization": `DPoP ${accessToken}`
* }
* });
* ```
*/
static generateProof({ url, method, dpopKeyPair, nonce, accessToken, }: DPoPGenerateProofConfig): Promise<string>;
}
declare const ERR_DESCRIPTION: {
TOKEN_PROVIDER: {
REQUIRED: string;
MISSING_ACCESS_TOKEN: string;
MISSING_TOKEN_TYPE: string;
UNSUPPORTED_TOKEN_TYPE: string;
};
RESPONSE: {
BODY_PARSING_ERROR: string;
NON_SUCCESSFUL: (url: URL, method: RequestInit["method"], response: Response) => string;
};
DPOP: {
REQUIRED: string;
INVALID_INSTANCE: string;
PRIVATE_KEY_NON_EXPORTABLE: string;
PRIVATE_KEY_SIGN_USAGE: string;
};
CRYPTO: {
UNSUPPORTED_PUBLIC_KEY_TYPE: string;
UNSUPPORTED_ALGORITHM: string;
UNSUPPORTED_ALGORITHM_CONFIGURATION: string;
UNSUPPORTED_RSA_HASH_ALGORITHM: string;
INVALID_RSA_MODULUS_LENGTH: string;
UNSUPPORTED_ECDSA_CURVE: string;
UNSUPPORTED_RSA_PSS_HASH_ALGORITHM: string;
};
};
/**
* @internal
* Base error class.
*/
declare class BaseError extends Error {
/**
* @internal
*/
constructor(message: string);
}
/**
* Error thrown when the configuration is invalid.
*
* @group Errors
*/
declare class ConfigurationError extends BaseError {
/**
* @internal
*/
constructor(message: string);
}
/**
* Error thrown when there's an issue with the token provider.
*
* @group Errors
*/
declare class TokenProviderError extends BaseError {
/**
* @internal
*/
constructor(message: string);
}
/**
* Error thrown when non-successful API response is returned.
*
* @group Errors
*/
declare class ApiResponseError extends BaseError {
readonly response: Response;
readonly status: number;
readonly statusText: string;
readonly body?: unknown;
/**
* @internal
*/
constructor(message: string, response: Response, body?: unknown);
}
export { AbstractTokenProvider, ApiResponseError, BaseError, ConfigurationError, DPOP_SUPPORTED_ALGORITHMS, type DPoPGenerateProofConfig, type DPoPKeyGenConfig, type DPoPKeyPair, type DPoPSupportedAlgorithms, type DPoPSupportedCurveOrModulus, DPoPUtils, ERR_DESCRIPTION, HTTP_CONTENT_TYPE, HTTP_METHOD, type HttpContentType, type HttpMethod, OAuthFetch, type OAuthFetchPrivateResourceConfig, type OAuthFetchPublicResourceConfig, type RequestBody, type RequestOptions, SUPPORTED_TOKEN_TYPES, TokenProviderError, type TokenProviderGetTokenResponse, type TokenProviderTokenType };