-
Notifications
You must be signed in to change notification settings - Fork 2
feat: add Multiple Custom Domains (MCD) support and fix JWT verification #71
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
kishore7snehil
wants to merge
17
commits into
main
Choose a base branch
from
feat/mcd-support
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 8 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
ac24ef7
feat: add Multiple Custom Domains (MCD) support and fix JWT verification
kishore7snehil 1164848
Bump poetry version from latest to 2.2.1 in test workflow
kishore7snehil 2646682
Fix linting errors
kishore7snehil 307000b
test: improve cache verification in OIDC metadata and JWKS tests
kishore7snehil 3f68c3c
refactor: rename cache size variable and reorganize test comments
kishore7snehil 2e0ae17
chore: add cryptography package to Snyk license ignore list
kishore7snehil 3b131d8
Merge remote-tracking branch 'origin/main' into feat/mcd-support
kishore7snehil daa6d35
Implement normalized issuer validation and add related tests
kishore7snehil 74329ab
refactor: remove unused jwt import from test_server_client.py
kishore7snehil 073c38b
refactor: remove requirement comments for OIDC metadata and JWKS caching
kishore7snehil 75a8531
feat: improve caching mechanism and add more examples to the example doc
kishore7snehil 0eb64b4
feat: enhance Multiple Custom Domains (MCD) support with dynamic domaβ¦
kishore7snehil f8d8470
fix: update type hint for audience parameter in _verify_and_decode_jwβ¦
kishore7snehil 283377f
feat: add DOMAIN_MISMATCH error code for session domain validation inβ¦
kishore7snehil e40942d
feat: implement domain validation for backchannel logout and enhance β¦
kishore7snehil 1c5f2d8
chore: fix for review comments (set-1)
kishore7snehil a9debdd
fix: scoped logout, backchannel logout
kishore7snehil 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,139 @@ | ||
| # Multiple Custom Domains (MCD) Guide | ||
|
|
||
| This guide explains how to implement Multiple Custom Domain (MCD) support using the Auth0 Python SDKs. | ||
|
|
||
| ## What is MCD? | ||
|
|
||
| Multiple Custom Domains (MCD) allows your application to serve different organizations or tenants from different hostnames, each mapping to a different Auth0 tenant/domain. | ||
|
|
||
| **Example:** | ||
| - `https://acme.yourapp.com` β Auth0 tenant: `acme.auth0.com` | ||
| - `https://globex.yourapp.com` β Auth0 tenant: `globex.auth0.com` | ||
|
|
||
| Each tenant gets its own branded login experience while using a single application codebase. | ||
|
|
||
| ## Configuration Methods | ||
|
|
||
| ### Method 1: Static Domain (Single Tenant) | ||
|
|
||
| For applications with a single Auth0 domain: | ||
|
|
||
| ```python | ||
| from auth0_server_python import ServerClient | ||
|
|
||
| client = ServerClient( | ||
| domain="your-tenant.auth0.com", # Static string | ||
| client_id="your_client_id", | ||
| client_secret="your_client_secret", | ||
| secret="your_encryption_secret" | ||
| ) | ||
| ``` | ||
|
|
||
| ### Method 2: Dynamic Domain Resolver (MCD) | ||
|
|
||
| For MCD support, provide a domain resolver function that receives a `DomainResolverContext`: | ||
|
|
||
| ```python | ||
| from auth0_server_python import ServerClient | ||
| from auth0_server_python.auth_types import DomainResolverContext | ||
|
|
||
| # Map your app hostnames to Auth0 domains | ||
| DOMAIN_MAP = { | ||
| "acme.yourapp.com": "acme.auth0.com", | ||
| "globex.yourapp.com": "globex.auth0.com", | ||
| } | ||
| DEFAULT_DOMAIN = "default.auth0.com" | ||
|
|
||
| async def domain_resolver(context: DomainResolverContext) -> str: | ||
| """ | ||
| Resolve Auth0 domain based on request hostname. | ||
|
|
||
| Args: | ||
| context: Contains request_url and request_headers | ||
|
|
||
| Returns: | ||
| Auth0 domain string (e.g., "acme.auth0.com") | ||
| """ | ||
| # Extract hostname from request headers | ||
| if not context.request_headers: | ||
| return DEFAULT_DOMAIN | ||
|
|
||
| host = context.request_headers.get('host', DEFAULT_DOMAIN) | ||
| host_without_port = host.split(':')[0] | ||
|
|
||
| # Look up Auth0 domain | ||
| return DOMAIN_MAP.get(host_without_port, DEFAULT_DOMAIN) | ||
|
|
||
| client = ServerClient( | ||
| domain=domain_resolver, # Callable function | ||
| client_id="your_client_id", | ||
| client_secret="your_client_secret", | ||
| secret="your_encryption_secret" | ||
| ) | ||
| ``` | ||
|
|
||
| ## DomainResolverContext | ||
|
|
||
| The `DomainResolverContext` object provides request information to your resolver: | ||
|
|
||
| | Property | Type | Description | | ||
| |----------|------|-------------| | ||
| | `request_url` | `Optional[str]` | Full request URL (e.g., "https://acme.yourapp.com/auth/login") | | ||
| | `request_headers` | `Optional[dict[str, str]]` | Request headers dictionary | | ||
|
|
||
| **Common headers:** | ||
| - `host`: Request hostname (e.g., "acme.yourapp.com") | ||
| - `x-forwarded-host`: Original host when behind proxy/load balancer | ||
|
|
||
| **Example usage:** | ||
|
|
||
| ```python | ||
| async def domain_resolver(context: DomainResolverContext) -> str: | ||
| # Check if we have request headers | ||
| if not context.request_headers: | ||
| return DEFAULT_DOMAIN | ||
|
|
||
| # Use x-forwarded-host if behind proxy, otherwise use host | ||
| host = (context.request_headers.get('x-forwarded-host') or | ||
| context.request_headers.get('host', '')) | ||
|
|
||
| # Remove port number if present | ||
| hostname = host.split(':')[0].lower() | ||
|
|
||
| # Look up in mapping | ||
| return DOMAIN_MAP.get(hostname, DEFAULT_DOMAIN) | ||
| ``` | ||
|
|
||
| ## Error Handling | ||
|
|
||
| ### DomainResolverError | ||
|
|
||
| The domain resolver should return a valid Auth0 domain string. Invalid returns will raise `DomainResolverError`: | ||
|
|
||
| ```python | ||
| from auth0_server_python.error import DomainResolverError | ||
|
|
||
| async def domain_resolver(context: DomainResolverContext) -> str: | ||
| try: | ||
| domain = lookup_domain_from_db(context) | ||
|
|
||
| if not domain: | ||
| # Return default instead of None | ||
| return DEFAULT_DOMAIN | ||
|
|
||
| return domain # Must be a non-empty string | ||
|
|
||
| except Exception as e: | ||
| # Log error and return default | ||
| logger.error(f"Domain resolution failed: {e}") | ||
| return DEFAULT_DOMAIN | ||
| ``` | ||
|
|
||
| **Invalid return values that raise `DomainResolverError`:** | ||
| - `None` | ||
| - Empty string `""` | ||
| - Non-string types (int, list, dict, etc.) | ||
|
|
||
| **Exceptions raised by your resolver:** | ||
| - Automatically wrapped in `DomainResolverError` | ||
| - Original exception accessible via `.original_error` | ||
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.