Skip to content

feat: add mTLS (RFC 8705) client authentication - #159

Open
cschetan77 wants to merge 9 commits into
mainfrom
feat/mtls-client-authentication
Open

feat: add mTLS (RFC 8705) client authentication#159
cschetan77 wants to merge 9 commits into
mainfrom
feat/mtls-client-authentication

Conversation

@cschetan77

Copy link
Copy Markdown

Summary

Adds Mutual TLS (RFC 8705) client authentication to auth0-server-python. When enabled, the SDK presents a TLS client certificate during the Auth0 token-endpoint handshake instead of a client secret — no credential travels in the request body.

  • Two new ServerClient constructor params: use_mtls: bool = False and ssl_context: Optional[ssl.SSLContext] = None. The caller builds the SSLContext (ssl.create_default_context() + load_cert_chain); the SDK forwards it as verify=ssl_context to every httpx.AsyncClient it constructs (including the authlib client used for the authorization-code exchange).
  • Endpoint routing: new _resolve_token_endpoint(metadata) helper returns mtls_endpoint_aliases.token_endpoint from the discovery document when mTLS is on, raising ConfigurationError if the alias is absent. All six token-endpoint call sites in server_client.py are routed through it.
  • Client authentication: _apply_client_authentication returns None under mTLS — the certificate in the TLS handshake is the sole credential; no client_secret or client_assertion is added to the body.
  • MFA under mTLS: MfaClient receives use_mtls and ssl_context from ServerClient (cert on all MFA calls). verify() accepts an optional token_endpoint_override so the mTLS alias can be passed in for the token exchange while challenge/enroll calls remain on the standard host.
  • DPoP mutual exclusion: signin_with_passkey and MfaClient.verify raise ConfigurationError when both dpop_key and use_mtls are active — DPoP would bind the token to its own key and suppress cnf.x5t#S256, silently defeating mTLS token binding.
  • cnf.x5t#S256 advisory warning: after obtaining a token under mTLS, _warn_if_not_cert_bound checks whether the access token is certificate-bound and emits a UserWarning if not (silent on opaque tokens; never raises).
  • Constructor validation (all ConfigurationError, fail-fast): use_mtls=True without ssl_context; combined with client_secret; combined with client_assertion_signing_key.

Changed files

File Change
auth_server/server_client.py use_mtls/ssl_context params, validation, _resolve_token_endpoint, _apply_client_authentication mTLS branch, _warn_if_not_cert_bound, DPoP guard, 6 call-site routings, _get_http_client + AsyncOAuth2Client verify= injection
auth_server/mfa_client.py use_mtls/ssl_context params, _get_http_client verify= injection, verify() token_endpoint_override param + DPoP guard
tests/test_server_client.py Constructor validation, _resolve_token_endpoint, _apply_client_authentication mTLS branch, SSLContext threading, _warn_if_not_cert_bound, DPoP exclusion, end-to-end alias-routing test
tests/test_mfa_client.py SSLContext threading, token_endpoint_override, DPoP exclusion
examples/MutualTLS.md New per-feature guide: prerequisites, cert generation, wiring, mutual-exclusion rules, cnf verification
README.md mTLS section linking to the guide
references/flow-map.md mTLS row

Test plan

  • poetry run pytest — all tests pass (491 tests)
  • poetry run ruff check . — no lint errors
  • Constructor raises ConfigurationError for: missing ssl_context with use_mtls=True; client_secret + use_mtls; client_assertion_signing_key + use_mtls
  • _resolve_token_endpoint returns the mTLS alias when present, raises ConfigurationError when absent under mTLS, returns the standard endpoint when mTLS is off
  • complete_interactive_login under mTLS hits mtls_endpoint_aliases.token_endpoint
  • signin_with_passkey raises ConfigurationError when dpop_key + use_mtls
  • mfa.verify raises ConfigurationError when dpop_key + use_mtls; uses token_endpoint_override when provided
  • _warn_if_not_cert_bound warns on JWT without cnf.x5t#S256, silent on JWT with it, silent on opaque token

@cschetan77
cschetan77 requested a review from a team as a code owner August 21, 2026 09:30
@cschetan77
cschetan77 force-pushed the feat/mtls-client-authentication branch from 9ef2878 to 0915865 Compare August 21, 2026 09:37
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.

1 participant