How AuthClient Handles Metadata Discovery and Caching for OAuth Server Configuration

The AuthClient performs an OpenID Connect discovery request to the issuer's well-known endpoint, stores the resulting ServerMetadata in a shared LRU cache with a configurable TTL (default 600 seconds), and deduplicates concurrent calls through an in-flight promise map to optimize performance across the process lifetime.

The auth0/auth0-auth-js library provides a production-ready OAuth 2.0 client that automatically handles OpenID Connect discovery. Understanding how the AuthClient manages metadata discovery and caching is essential for optimizing authentication flows in high-throughput applications. This implementation ensures that discovery requests are both efficient and resilient by leveraging a global caching strategy with built-in request deduplication.

The Lazy Discovery Flow

The AuthClient employs a lazy initialization pattern where discovery only occurs when a public method first requires server metadata. This happens transparently when calling methods like buildAuthorizationUrl(), getTokenByCode(), or getServerMetadata() from packages/auth0-auth-js/src/auth-client.ts.

Cache Key Generation

Before issuing any network request, the client computes a deterministic cache key in the #getDiscoveryCacheKey method (lines 55-58 of auth-client.ts). The key normalizes the tenant domain and incorporates the mTLS configuration flag using the format domain|mtls:0|1. This ensures that metadata for the same Auth0 tenant is reused even when different clients request it, while keeping mTLS and non-mTLS configurations isolated.

Deduplicating Concurrent Requests

To prevent redundant network calls when multiple methods trigger discovery simultaneously, the #discover method (lines 102-112 of auth-client.ts) maintains a private #inFlightDiscovery map. If a discovery request is already pending for a given cache key, subsequent callers await the existing promise rather than initiating a new HTTP request. This guarantees that only one discovery request per tenant exists in-flight at any moment, regardless of application concurrency.

Global LRU Cache Architecture

The caching layer is implemented through a sophisticated factory pattern that promotes cache reuse across process boundaries. The DiscoveryCacheFactory defined in packages/auth0-auth-js/src/cache-provider.ts (lines 83-100) manages a global cache map (globalCaches) that ensures all AuthClient instances with identical cache configurations share the same underlying storage.

The resolveCacheConfig function (lines 65-76 of cache-provider.ts) normalizes user-provided options with sensible defaults:

  • TTL: 600 seconds (10 minutes)
  • Max entries: 100 tenants

The concrete implementation uses an internal LruCache class (located in packages/auth0-auth-js/src/lru-cache.ts) that provides per-entry TTL expiration and least-recently-used eviction semantics.

Practical Implementation

When you instantiate an AuthClient, no network activity occurs until a method requires server metadata:

import { AuthClient } from '@auth0/auth0-auth-js';

// Initialization does not trigger discovery
const auth = new AuthClient({
  domain: 'tenant.auth0.com',
  clientId: 'CLIENT_ID',
  clientSecret: 'CLIENT_SECRET',
});

// First call triggers discovery and populates cache
const authUrl = await auth.buildAuthorizationUrl();

Subsequent operations against the same instance reuse the cached metadata instantly:

// No HTTP request performed; uses cached ServerMetadata
const tokens = await auth.getTokenByCode(callbackUrl, { codeVerifier });

Cross-Instance Cache Sharing

Because the underlying LRU cache is global, separate AuthClient instances targeting the same domain automatically benefit from shared metadata:

const auth2 = new AuthClient({
  domain: 'tenant.auth0.com',  // Same tenant
  clientId: 'OTHER_CLIENT_ID',
  clientSecret: 'OTHER_SECRET',
});

// Returns immediately from global cache populated by first client
const metadata = await auth2.getServerMetadata();

Configuring Cache Behavior

You can customize the discovery cache through the discoveryCache option in the AuthClient constructor. The configuration flows through resolveCacheConfig to the global cache factory:

const auth = new AuthClient({
  domain: 'tenant.auth0.com',
  clientId: 'CLIENT_ID',
  clientSecret: 'CLIENT_SECRET',
  discoveryCache: {
    ttl: 300,        // 5 minutes instead of 10
    maxEntries: 200  // Increase tenant capacity
  }
});

These parameters affect the cache identity in the global map, meaning instances with different TTL or size limits maintain separate cache stores.

Summary

  • Lazy discovery ensures the first method call requiring metadata triggers a single OpenID Connect discovery request to /.well-known/openid-configuration.
  • Global LRU caching shares metadata across all AuthClient instances with matching configurations, keyed by normalized domain and mTLS flag.
  • Request deduplication via the #inFlightDiscovery map prevents duplicate network calls during concurrent access patterns.
  • Configurable TTL defaults to 600 seconds (10 minutes) and can be adjusted per client instance through DiscoveryCacheOptions.
  • Automatic reuse applies to all public methods including buildAuthorizationUrl, getTokenByCode, and getServerMetadata.

Frequently Asked Questions

How does AuthClient prevent duplicate discovery requests for the same tenant?

The AuthClient maintains a private #inFlightDiscovery map in auth-client.ts that tracks pending discovery promises keyed by the normalized domain and mTLS flag. When multiple methods trigger discovery concurrently, subsequent callers await the existing in-flight promise rather than initiating new HTTP requests, ensuring only one network call per tenant occurs regardless of application concurrency.

What is the default TTL for OAuth server metadata in AuthClient?

By default, the AuthClient caches server metadata for 600 seconds (10 minutes). This default is set in resolveCacheConfig within packages/auth0-auth-js/src/cache-provider.ts and applies to the global LRU cache unless overridden through the discoveryCache.ttl configuration option.

Is the discovery cache shared across multiple AuthClient instances?

Yes. The DiscoveryCacheFactory creates cache instances through a global map (globalCaches), meaning all AuthClient instances with identical ttl and maxEntries configurations share the same underlying LRU cache. This design allows metadata retrieved by one client instance to be instantly available to others targeting the same Auth0 domain.

How does the cache key differentiate between mTLS and standard connections?

The #getDiscoveryCacheKey method appends an mTLS indicator to the normalized domain using the format domain|mtls:0|1, where 1 indicates mutual TLS is enabled. This ensures that mTLS-specific metadata endpoints are cached separately from standard OAuth configurations, preventing credential context contamination between different authentication modes.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →