# How Auth0-Auth-JS Handles mTLS Authentication with customFetch: A Complete Technical Guide

> Explore how Auth0-Auth-JS leverages customFetch for seamless mTLS authentication. Learn to delegate certificate handling and enhance your secure HTTP requests.

- Repository: [Auth0/auth0-auth-js](https://github.com/auth0/auth0-auth-js)
- Tags: deep-dive
- Published: 2026-02-25

---

**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`](https://github.com/auth0/auth0-auth-js/blob/main/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.

```typescript
// 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`](https://github.com/auth0/auth0-auth-js/blob/main/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`.

```typescript
// 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:

```typescript
// 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`](https://github.com/auth0/auth0-auth-js/blob/main/packages/auth0-auth-js/src/auth-client.ts) generates a composite key:

```typescript
// 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`).

```typescript
// 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`](https://github.com/auth0/auth0-auth-js/blob/main/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:

```typescript
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:

```typescript
// 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 `useMtls` and `customFetch` in `AuthClientOptions` ([`types.ts`](https://github.com/auth0/auth0-auth-js/blob/main/types.ts)).
* **Validation**: Constructor throws `NotSupportedError` if mTLS is enabled without a custom fetch ([`auth-client.ts`](https://github.com/auth0/auth0-auth-js/blob/main/auth-client.ts)).
* **Telemetry**: Wraps user-provided fetch with `createTelemetryFetch` to maintain SDK headers.
* **Caching**: Uses separate discovery cache keys for mTLS vs. standard flows via `#getDiscoveryCacheKey`.
* **Endpoint Selection**: Passes `use_mtls_endpoint_aliases` to leverage `mtls_endpoint_aliases` from discovery documents.
* **Delegation**: Relies entirely on the consumer's `customFetch` to 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`](https://github.com/auth0/auth0-auth-js/blob/main/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`](https://github.com/auth0/auth0-auth-js/blob/main/packages/auth0-auth-js/src/auth-client.spec.ts).