-
Notifications
You must be signed in to change notification settings - Fork 8
feat: add mTLS (RFC 8705) client authentication #159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
cschetan77
wants to merge
19
commits into
main
Choose a base branch
from
feat/mtls-client-authentication
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
7fbe52a
feat: add use_mtls and ssl_context constructor args with validation
cschetan77 19b1484
feat: pass mTLS ssl_context to httpx and authlib clients
cschetan77 7f18573
feat: add _resolve_token_endpoint mTLS alias resolver
cschetan77 8acc86b
feat: return no body credential under mTLS in client auth resolver
cschetan77 be1fee8
feat: route all token-endpoint calls through mTLS alias resolver
cschetan77 8f786c9
feat: reject dpop_key + use_mtls in signin_with_passkey
cschetan77 3849b96
feat: warn when mTLS token lacks cnf.x5t#S256 binding
cschetan77 09bf72f
feat: thread mTLS ssl_context and alias routing through MFA verify
cschetan77 5758f63
docs: document mTLS client authentication
cschetan77 14430d6
docs: document token_endpoint_override and dpop+mTLS ConfigurationErr…
cschetan77 0398bc6
style: apply repo conventions to mTLS code and docs
cschetan77 f557dd1
refactor(tests): distribute mTLS tests next to their surfaces
cschetan77 2f26875
docs: link to Auth0 mTLS configuration docs in README
cschetan77 5bc3fec
fix: route mTLS token calls through alias resolver in MFA, passwordle…
cschetan77 4a03a5a
refactor: replace cnf.x5t#S256 UserWarning with documentation
cschetan77 e620c26
docs: document passkey challenge/register incompatibility with mTLS-o…
cschetan77 9cd5951
feat: wire ssl_context through MyAccountClient for mTLS cert-bound to…
cschetan77 e46e28b
fix: route PAR endpoint through mTLS alias when use_mtls is enabled
cschetan77 82cd04c
test: add mTLS token endpoint routing assertions for refresh, backcha…
cschetan77 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| # Mutual TLS (mTLS) Client Authentication | ||
|
|
||
| Authenticate to Auth0 with a TLS client certificate instead of a client secret (RFC 8705). The certificate is presented during the TLS handshake; no credential travels in the request body. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - Auth0 **Enterprise** tenant with the **Highly Regulated Identity** add-on | ||
| - A `self_managed_certs` **custom domain** configured on the tenant | ||
| - **Allow mTLS Endpoint Aliases** enabled on the tenant (Dashboard → Settings → Advanced) | ||
| - Client application's authentication method set to **mTLS** in Dashboard → Applications → Settings → Credentials | ||
|
|
||
| ## Generating a client certificate (development) | ||
|
|
||
| ```bash | ||
| # Self-signed CA + client cert (development only - use your PKI in production) | ||
| openssl req -x509 -newkey rsa:4096 -keyout ca.key -out ca.crt -days 365 -nodes \ | ||
| -subj "/CN=dev-ca" | ||
| openssl req -newkey rsa:2048 -keyout client.key -out client.csr -nodes \ | ||
| -subj "/CN=my-app-client" | ||
| openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ | ||
| -out client.crt -days 365 | ||
| ``` | ||
|
|
||
| ## Wiring into `ServerClient` | ||
|
|
||
| ```python | ||
| import ssl | ||
| from auth0_server_python.auth_server.server_client import ServerClient | ||
|
|
||
| ssl_context = ssl.create_default_context() # trusts system/public CAs for the server side | ||
| ssl_context.load_cert_chain("client.crt", "client.key") # attaches the client identity | ||
|
|
||
| auth0 = ServerClient( | ||
| domain="login.example.com", # self_managed_certs custom domain | ||
| client_id="<AUTH0_CLIENT_ID>", | ||
| use_mtls=True, | ||
| ssl_context=ssl_context, | ||
| secret="<AUTH0_SECRET>", | ||
| authorization_params={ | ||
| "audience": "<API_IDENTIFIER>", | ||
| "scope": "openid profile email offline_access", | ||
| }, | ||
| ) | ||
| ``` | ||
|
|
||
| The SDK passes `ssl_context` as `verify=ssl_context` to every `httpx.AsyncClient` it constructs, including the authlib client used for the authorization-code exchange. You never call `load_cert_chain` inside the SDK - the caller owns the TLS material. | ||
|
|
||
| ## Mutual exclusion | ||
|
|
||
| `use_mtls=True` cannot be combined with: | ||
|
|
||
| | Parameter | Reason | | ||
| |-----------|--------| | ||
| | `client_secret` | One client-auth method only - Auth0 rejects requests carrying both. | | ||
| | `client_assertion_signing_key` | Same - one method only. | | ||
| | `dpop_key` (per-call on `signin_with_passkey` / `mfa.verify`) | DPoP binds to its own key (`cnf.jkt`) and suppresses `cnf.x5t#S256`; combining them silently defeats mTLS token binding. | | ||
|
|
||
| All three raise `ConfigurationError` immediately (constructor for the first two, at the call site for DPoP). | ||
|
cschetan77 marked this conversation as resolved.
|
||
|
|
||
| ## Token sender-constraining | ||
|
|
||
| When the target API has **Token Sender-Constraining (mTLS)** enabled, issued access tokens carry a `cnf.x5t#S256` claim binding the token to the certificate thumbprint. If your tokens do not contain this claim, enable **Token Sender-Constraining (mTLS)** on the API resource server in the Auth0 dashboard. | ||
|
|
||
| To verify the thumbprint yourself: | ||
|
|
||
| ```bash | ||
| openssl x509 -in client.crt -outform DER | openssl dgst -sha256 -binary | openssl enc -base64 | tr '+/' '-_' | tr -d '=' | ||
| # Compare the output to the cnf.x5t#S256 claim in the decoded access token. | ||
| ``` | ||
|
|
||
| ## MFA under mTLS | ||
|
|
||
| The client certificate is presented on all MFA API calls. The token-endpoint call inside `mfa.verify` is routed through the mTLS alias automatically. Challenge and enrollment calls stay on the standard host, which does not request a client certificate. | ||
|
|
||
| ```python | ||
| await auth0.mfa.verify( | ||
| {"mfa_token": encrypted_token, "otp": "123456"}, | ||
| ) | ||
| ``` | ||
|
|
||
| ## Passkeys under mTLS | ||
|
|
||
| `/passkey/challenge` and `/passkey/register` are not served on the mTLS endpoint aliases. Auth0 only accepts `client_secret` as the credential on those endpoints - the client certificate is not a valid credential there. | ||
|
|
||
| Because `use_mtls=True` forbids `client_secret` at construction time, an mTLS-configured client has no valid credential for `passkey_login_challenge` and `passkey_signup_challenge`. Those calls will be rejected by Auth0 if the application is registered as a confidential client. | ||
|
|
||
| `signin_with_passkey` (the token-exchange step) is not affected - it calls the token endpoint, which is served on the mTLS alias and routed correctly. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.