How Auth0-Auth-JS Handles mTLS Authentication with customFetch: A Complete Technical Guide
The Auth0-Auth-JS SDK supports mutual TLS (mTLS) authentication by delegating TLS certificate handling to a user-provided customFetch implementation, which the SDK validates, wraps with telemetry, and injects into all downstream HTTP requests.
The auth0/auth0-auth-js repository provides a TypeScript SDK for authentication flows. When implementing mTLS authentication with customFetch, the SDK does not manage client certificates directly; instead, it relies on the consumer to supply a fetch implementation configured with the necessary TLS agents or certificates. This article examines the source code to explain how the SDK validates, processes, and utilizes custom fetch implementations for mTLS.
Configuration Surface for mTLS
The SDK exposes mTLS support through the AuthClientOptions interface defined in packages/auth0-auth-js/src/types.ts. Two properties control the behavior:
useMtls?: boolean– Signals the SDK to target mTLS-specific endpoints when available.customFetch?: typeof fetch– The user-provided fetch implementation responsible for attaching client certificates.
// packages/auth0-auth-js/src/types.ts
export interface AuthClientOptions {
// ... other options ...
/** Optional, custom Fetch implementation to use. */
customFetch?: typeof fetch;
/**
* Indicates whether the SDK should use the mTLS endpoints if they are available.
*
* When set to `true`, using a `customFetch` is required.
*/
useMtls?: boolean;
}
Constructor Validation and Error Handling
When instantiating AuthClient, the constructor in packages/auth0-auth-js/src/auth-client.ts performs strict validation. If useMtls is enabled but no customFetch is provided, the SDK throws a NotSupportedError with the specific code MTLS_WITHOUT_CUSTOMFETCH_NOT_SUPPORT.
// packages/auth0-auth-js/src/auth-client.ts
if (options.useMtls && !options.customFetch) {
throw new NotSupportedError(
NotSupportedErrorCode.MTLS_WITHOUT_CUSTOMFETCH_NOT_SUPPORT,
'Using mTLS without a custom fetch implementation is not supported'
);
}
This enforcement ensures that developers cannot accidentally enable mTLS without the necessary infrastructure to present client certificates.
Telemetry Wrapping for customFetch
Even when a custom implementation is supplied, the SDK decorates it with telemetry headers (such as Auth0-Client) via the createTelemetryFetch helper. This occurs in the AuthClient constructor:
// packages/auth0-auth-js/src/auth-client.ts
this.#customFetch = createTelemetryFetch(
options.customFetch ?? ((...args) => fetch(...args)),
getTelemetryConfig(options.telemetry)
);
The resulting #customFetch is stored as a private field and subsequently injected into all internal openid-client configurations.
Discovery Cache Strategy for mTLS
Metadata discovery (/.well-known/openid-configuration) is cached per domain and per the useMtls flag. The private method #getDiscoveryCacheKey in packages/auth0-auth-js/src/auth-client.ts generates a composite key:
// packages/auth0-auth-js/src/auth-client.ts
#getDiscoveryCacheKey(): string {
const domain = this.#options.domain.toLowerCase();
return `${domain}|mtls:${this.#options.useMtls ? '1' : '0'}`;
}
This separation ensures that mTLS-aware metadata (which may contain mtls_endpoint_aliases) does not collide with standard metadata caches.
Token Endpoint Selection via mtls_endpoint_aliases
When useMtls is enabled, the SDK passes the option { use_mtls_endpoint_aliases: true } to the token request. The underlying openid-client library then inspects the discovery document for mtls_endpoint_aliases, automatically routing requests to the mTLS-specific token endpoint (e.g., oauth/mtls/token instead of oauth/token).
// packages/auth0-auth-js/src/auth-client.ts (token request excerpt)
{
// ... other parameters ...
{ use_mtls_endpoint_aliases: this.#options.useMtls }
}
If the tenant does not publish mtls_endpoint_aliases, the SDK gracefully falls back to the standard token endpoint, as verified in auth-client.spec.ts.
Practical Implementation Examples
Node.js mTLS Setup with node-fetch
In Node.js environments, you typically use node-fetch combined with an https.Agent to attach client certificates:
import { AuthClient } from '@auth0/auth0-auth-js';
import fetch from 'node-fetch';
import https from 'https';
import { readFileSync } from 'fs';
const mtlsFetch = (url: string, init?: RequestInit) => {
const agent = new https.Agent({
cert: readFileSync('certs/client.crt'), // PEM-encoded client certificate
key: readFileSync('certs/client.key'), // PEM-encoded private key
// optionally: ca: readFileSync('certs/ca.pem')
});
return fetch(url, { ...init, agent });
};
const client = new AuthClient({
domain: 'my-tenant.auth0.com',
clientId: 'YOUR_CLIENT_ID',
useMtls: true,
customFetch: mtlsFetch,
});
Browser Service Worker Approach
In browser environments where direct TLS certificate access is restricted, a Service Worker can intercept requests and add client certificate headers that a proxy translates to TLS credentials:
// Service Worker intercepting Auth0 requests
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (url.origin === 'https://my-tenant.auth0.com') {
const modified = new Request(event.request, {
headers: { 'x-client-cert': window.myClientCert }
});
event.respondWith(fetch(modified));
}
});
// Main thread SDK initialization
const client = new AuthClient({
domain: 'my-tenant.auth0.com',
clientId: 'YOUR_CLIENT_ID',
useMtls: true,
customFetch: (input, init) => fetch(input, init), // Service Worker handles the rest
});
Summary
The Auth0-Auth-JS SDK implements mTLS authentication with customFetch through the following mechanisms:
- Configuration: Exposes
useMtlsandcustomFetchinAuthClientOptions(types.ts). - Validation: Constructor throws
NotSupportedErrorif mTLS is enabled without a custom fetch (auth-client.ts). - Telemetry: Wraps user-provided fetch with
createTelemetryFetchto maintain SDK headers. - Caching: Uses separate discovery cache keys for mTLS vs. standard flows via
#getDiscoveryCacheKey. - Endpoint Selection: Passes
use_mtls_endpoint_aliasesto leveragemtls_endpoint_aliasesfrom discovery documents. - Delegation: Relies entirely on the consumer's
customFetchto attach client certificates, keeping the SDK agnostic of TLS implementation details.
Frequently Asked Questions
What happens if I enable useMtls but forget to provide a customFetch?
The SDK constructor immediately throws a NotSupportedError with the code MTLS_WITHOUT_CUSTOMFETCH_NOT_SUPPORT. This validation in packages/auth0-auth-js/src/auth-client.ts ensures that developers cannot accidentally attempt mTLS without the necessary infrastructure to present client certificates.
Does the SDK modify my custom fetch implementation?
The SDK wraps your customFetch with createTelemetryFetch to inject Auth0-Client telemetry headers, but it does not modify the underlying TLS configuration or certificate handling. Your implementation remains responsible for attaching client certificates via Node.js https.Agent, browser Service Workers, or other transport mechanisms.
How does the SDK handle tenants that do not support mTLS endpoints?
If the discovery document lacks mtls_endpoint_aliases, the SDK gracefully falls back to the standard token endpoint. The use_mtls_endpoint_aliases flag is passed to the underlying openid-client, which automatically handles the fallback logic. This behavior is verified in the test suite at packages/auth0-auth-js/src/auth-client.spec.ts.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →