Skip to content

feat(mTLS): add Mutual TLS (RFC 8705) client authentication support#851

Open
cschetan77 wants to merge 1 commit into
masterfrom
feat/mtls
Open

feat(mTLS): add Mutual TLS (RFC 8705) client authentication support#851
cschetan77 wants to merge 1 commit into
masterfrom
feat/mtls

Conversation

@cschetan77

Copy link
Copy Markdown
Contributor

Summary

Implements RFC 8705 Mutual TLS client authentication, allowing applications to authenticate with Auth0 using a TLS client certificate instead of a clientSecret. When the IdP supports it, issued access tokens will carry a cnf.x5t#S256 claim binding them to the client certificate fingerprint (certificate-bound tokens).

  • useMtls: boolean — new opt-in config flag (default false); also settable via AUTH0_MTLS=true env var
  • When useMtls: true, the SDK switches client authentication from ClientSecretPost to TlsClientAuth() (openid-client v6 / oauth4webapi) and sets use_mtls_endpoint_aliases: true on client metadata so all token/userinfo requests are routed to the mtls_endpoint_aliases advertised in the OIDC discovery document
  • customFetch is required when useMtls: true — the standard fetch global has no API for attaching client certificates; a TLS-aware implementation (e.g. undici Agent with connect: { key, cert }) must be provided. A MtlsError with code mtls_requires_custom_fetch is thrown at auth() initialization time if it is missing (fail-fast)
  • clientSecret is no longer required when useMtls: true — the certificate is the sole client credential
  • New MtlsError class and MtlsErrorCode enum exported from the package for structured error handling
  • Graceful fallback: if the discovery document has no mtls_endpoint_aliases, the SDK falls back to the standard endpoints without throwing

Usage

const { Agent, fetch: undiciFetch } = require('undici');
const { readFileSync } = require('fs');
const { auth } = require('express-openid-connect');

const tlsAgent = new Agent({
  connect: {
    key: readFileSync(process.env.MTLS_CLIENT_KEY_PATH),
    cert: readFileSync(process.env.MTLS_CLIENT_CERT_PATH),
  },
});

app.use(auth({
  useMtls: true,
  customFetch: (url, options) =>
    undiciFetch(url, { ...options, dispatcher: tlsAgent }),
  // clientSecret is not needed
}));

Files changed

File Change
lib/errors.js New MtlsError class and MtlsErrorCode
lib/config.js useMtls Joi option, AUTH0_MTLS env var, fail-fast validation, tls_client_auth added to valid clientAuthMethod values, relaxed clientSecret requirement
lib/client.js tls_client_auth case in getClientAuth() calling TlsClientAuth(), use_mtls_endpoint_aliases on client metadata
index.js Export MtlsError / MtlsErrorCode
index.d.ts useMtls in ConfigParams, export MtlsError class and MtlsErrorCode const with full JSDoc
test/config.tests.js 8 config validation tests
test/client.tests.js 6 client-level tests

Test plan

  • All 334 existing tests still pass
  • useMtls defaults to false
  • useMtls: true + customFetch → no throw, clientAuthMethod resolves to tls_client_auth
  • useMtls: true without customFetch → throws MtlsError with mtls_requires_custom_fetch
  • AUTH0_MTLS=true env var respected
  • clientSecret not required when useMtls: true
  • use_mtls_endpoint_aliases: true set on clientMetadata
  • mtls_endpoint_aliases from discovery document surfaced in serverMetadata
  • Graceful fallback when discovery has no mtls_endpoint_aliases
  • customFetch is invoked during discovery when useMtls: true

References

Implements RFC 8705 mTLS client authentication, allowing applications to
authenticate with Auth0 using a TLS client certificate instead of a
client_secret. Certificate-bound access tokens (cnf.x5t#S256) are issued
automatically when the IdP supports them.

Key changes:
- New `useMtls` config option (also via AUTH0_MTLS env var); defaults to false
- When enabled, switches client auth method to `tls_client_auth` (TlsClientAuth()
  from openid-client v6) and sets `use_mtls_endpoint_aliases: true` so token/userinfo
  requests are routed to the mtls_endpoint_aliases advertised in discovery
- `customFetch` is required when useMtls=true; throws MtlsError at init time if missing
- `clientSecret` is no longer required when useMtls=true — the cert is the credential
- New MtlsError / MtlsErrorCode exported from the package for structured error handling
- 14 new tests covering config validation, clientMetadata flag, endpoint alias routing,
  customFetch invocation and graceful fallback when aliases are absent
@cschetan77
cschetan77 requested a review from a team as a code owner July 20, 2026 05:08
Comment thread test/config.tests.js
@@ -1,6 +1,7 @@
const { assert } = require('chai');
const { assert, expect } = require('chai');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants