From fd4a9a7186c75f0deab67de1c4f5f558df5d744e Mon Sep 17 00:00:00 2001 From: Sourav Basu Date: Thu, 20 Aug 2026 16:08:52 +0530 Subject: [PATCH 01/10] docs: Added docs for anonymous sessions --- EXAMPLES.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 14 ++++++++++++ 2 files changed, 76 insertions(+) diff --git a/EXAMPLES.md b/EXAMPLES.md index 9f431ac..c9a7a1b 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -273,3 +273,65 @@ async def verify_dpop_token(access_token, dpop_proof, http_method, http_url): "proof_claims": proof_claims } ``` + +## Anonymous Callers + +Anonymous Sessions give a visitor an Auth0 identity before they log in. The access token issued for an anonymous session is a standard Auth0 Bearer JWT, so this SDK validates it like any other token. The one difference is the `sub` claim, which starts with `anon@`. + +An anonymous token passes verification by default. To treat anonymous callers differently, or block them, check the `sub` claim after verifying the token. The SDK does not make that authorization decision for you. + +### Serve everyone, branch in the handler + +```python +import asyncio +from auth0_api_python import ApiClient, ApiClientOptions + +async def handle_cart(headers): + api_client = ApiClient(ApiClientOptions( + domain="your-tenant.auth0.com", + audience="https://api.example.com" + )) + + claims = await api_client.verify_request(headers=headers) + is_anonymous = claims.get("sub", "").startswith("anon@") + + if is_anonymous: + return {"cart": load_guest_cart(claims["sub"])} + return {"cart": load_user_cart(claims["sub"])} +``` + +### Block anonymous callers on a specific route + +```python +async def handle_checkout(headers): + api_client = ApiClient(ApiClientOptions( + domain="your-tenant.auth0.com", + audience="https://api.example.com" + )) + + claims = await api_client.verify_request(headers=headers) + if claims.get("sub", "").startswith("anon@"): + raise PermissionError("Anonymous callers are not allowed on this route") + + return {"order": create_order(claims["sub"])} +``` + +### Block anonymous callers everywhere + +The SDK has no global "reject anonymous" switch. Centralize the check in whatever shared layer your framework uses for auth (middleware, a FastAPI dependency, a decorator). + +```python +async def require_logged_in_user(headers): + api_client = ApiClient(ApiClientOptions( + domain="your-tenant.auth0.com", + audience="https://api.example.com" + )) + + claims = await api_client.verify_request(headers=headers) + if claims.get("sub", "").startswith("anon@"): + raise PermissionError("Anonymous callers are not allowed") + return claims +``` + +> [!NOTE] +> The `anon@` prefix on `sub` is the only signal that distinguishes an anonymous caller from a logged-in user. diff --git a/README.md b/README.md index 367685b..d865751 100644 --- a/README.md +++ b/README.md @@ -407,6 +407,20 @@ For hybrid mode (migration scenarios), resolver patterns, error handling, and ca - **[Multi-Custom Domain Guide](docs/MultipleCustomDomain.md)** - Configuration modes, resolver patterns, migration, error handling - **[Caching Guide](docs/Caching.md)** - Cache tuning, custom adapters (Redis, Memcached) +### 8. Anonymous Sessions + +[Anonymous Sessions](https://auth0.com/docs) give a visitor an Auth0 identity before they log in. The access token issued for an anonymous session is a standard Auth0 Bearer JWT, so `verify_access_token()` and `verify_request()` validate it exactly like any other token. The only difference is the `sub` claim, which starts with `anon@`. + +An anonymous token passes verification by default. If a route must not serve anonymous callers, check the `sub` claim after verification and reject it in your handler: + +```python +claims = await api_client.verify_request(headers=headers) +if claims.get("sub", "").startswith("anon@"): + raise PermissionError("Anonymous callers are not allowed on this route") +``` + +Deciding whether an anonymous caller is authorized is your application's responsibility. See [Anonymous Callers](EXAMPLES.md#anonymous-callers) for allow, block-per-route, and block-globally patterns. + ## Feedback ### Contributing From 6359d5cc3dff5411d15276b2b118c72a23d5665f Mon Sep 17 00:00:00 2001 From: Sourav Basu Date: Mon, 28 Sep 2026 11:28:39 +0530 Subject: [PATCH 02/10] docs: Added link to EXAMPLES.md for Anonymous Callers in README.md --- README.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index d865751..9b651bd 100644 --- a/README.md +++ b/README.md @@ -411,15 +411,7 @@ For hybrid mode (migration scenarios), resolver patterns, error handling, and ca [Anonymous Sessions](https://auth0.com/docs) give a visitor an Auth0 identity before they log in. The access token issued for an anonymous session is a standard Auth0 Bearer JWT, so `verify_access_token()` and `verify_request()` validate it exactly like any other token. The only difference is the `sub` claim, which starts with `anon@`. -An anonymous token passes verification by default. If a route must not serve anonymous callers, check the `sub` claim after verification and reject it in your handler: - -```python -claims = await api_client.verify_request(headers=headers) -if claims.get("sub", "").startswith("anon@"): - raise PermissionError("Anonymous callers are not allowed on this route") -``` - -Deciding whether an anonymous caller is authorized is your application's responsibility. See [Anonymous Callers](EXAMPLES.md#anonymous-callers) for allow, block-per-route, and block-globally patterns. +An anonymous token passes verification by default. Deciding whether an anonymous caller is authorized is your application's responsibility. See [Anonymous Callers](EXAMPLES.md#anonymous-callers) for allow, block-per-route, and block-globally patterns. ## Feedback From ba12db57e15c8d576d6f1c68cb42f7b2b8e704d9 Mon Sep 17 00:00:00 2001 From: Sourav Basu Date: Wed, 30 Sep 2026 23:15:00 +0530 Subject: [PATCH 03/10] docs: fix anonymous sessions link in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b651bd..31f3ff6 100644 --- a/README.md +++ b/README.md @@ -409,7 +409,7 @@ For hybrid mode (migration scenarios), resolver patterns, error handling, and ca ### 8. Anonymous Sessions -[Anonymous Sessions](https://auth0.com/docs) give a visitor an Auth0 identity before they log in. The access token issued for an anonymous session is a standard Auth0 Bearer JWT, so `verify_access_token()` and `verify_request()` validate it exactly like any other token. The only difference is the `sub` claim, which starts with `anon@`. +[Anonymous Sessions](https://auth0.com/docs/manage-users/sessions/anonymous-sessions) give a visitor an Auth0 identity before they log in. The access token issued for an anonymous session is a standard Auth0 Bearer JWT, so `verify_access_token()` and `verify_request()` validate it exactly like any other token. The only difference is the `sub` claim, which starts with `anon@`. An anonymous token passes verification by default. Deciding whether an anonymous caller is authorized is your application's responsibility. See [Anonymous Callers](EXAMPLES.md#anonymous-callers) for allow, block-per-route, and block-globally patterns. From d1f933844ace86ec083fa439b349243e01ec908e Mon Sep 17 00:00:00 2001 From: Sourav Basu Date: Wed, 30 Sep 2026 23:20:21 +0530 Subject: [PATCH 04/10] docs: rename Anonymous Sessions section to Anonymous Callers in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 31f3ff6..9754cee 100644 --- a/README.md +++ b/README.md @@ -407,9 +407,9 @@ For hybrid mode (migration scenarios), resolver patterns, error handling, and ca - **[Multi-Custom Domain Guide](docs/MultipleCustomDomain.md)** - Configuration modes, resolver patterns, migration, error handling - **[Caching Guide](docs/Caching.md)** - Cache tuning, custom adapters (Redis, Memcached) -### 8. Anonymous Sessions +### 8. Anonymous Callers -[Anonymous Sessions](https://auth0.com/docs/manage-users/sessions/anonymous-sessions) give a visitor an Auth0 identity before they log in. The access token issued for an anonymous session is a standard Auth0 Bearer JWT, so `verify_access_token()` and `verify_request()` validate it exactly like any other token. The only difference is the `sub` claim, which starts with `anon@`. +[Anonymous Sessions](https://auth0.com/docs/manage-users/sessions/anonymous-sessions) give a visitor an Auth0 identity before they log in. Session creation is handled by Auth0's web SDK. This SDK only validates the tokens they produce. The access token is a standard Auth0 Bearer JWT, so `verify_access_token()` and `verify_request()` validate it exactly like any other token. The only difference is the `sub` claim, which starts with `anon@`. An anonymous token passes verification by default. Deciding whether an anonymous caller is authorized is your application's responsibility. See [Anonymous Callers](EXAMPLES.md#anonymous-callers) for allow, block-per-route, and block-globally patterns. From d368de75886db8eef64f1a7e957beb11f4512faf Mon Sep 17 00:00:00 2001 From: Sourav Basu Date: Wed, 30 Sep 2026 23:24:48 +0530 Subject: [PATCH 05/10] docs: clarify anonymous session scope boundary in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9754cee..48df61f 100644 --- a/README.md +++ b/README.md @@ -409,7 +409,7 @@ For hybrid mode (migration scenarios), resolver patterns, error handling, and ca ### 8. Anonymous Callers -[Anonymous Sessions](https://auth0.com/docs/manage-users/sessions/anonymous-sessions) give a visitor an Auth0 identity before they log in. Session creation is handled by Auth0's web SDK. This SDK only validates the tokens they produce. The access token is a standard Auth0 Bearer JWT, so `verify_access_token()` and `verify_request()` validate it exactly like any other token. The only difference is the `sub` claim, which starts with `anon@`. +[Anonymous Sessions](https://auth0.com/docs/manage-users/sessions/anonymous-sessions) give a visitor an Auth0 identity before they log in. Session creation happens in your web application. This SDK only validates the tokens they produce. The access token is a standard Auth0 Bearer JWT, so `verify_access_token()` and `verify_request()` validate it exactly like any other token. The only difference is the `sub` claim, which starts with `anon@`. An anonymous token passes verification by default. Deciding whether an anonymous caller is authorized is your application's responsibility. See [Anonymous Callers](EXAMPLES.md#anonymous-callers) for allow, block-per-route, and block-globally patterns. From e5c159ebcca3743b4f58d7b902e89de4e0d8cfcf Mon Sep 17 00:00:00 2001 From: Sourav Basu Date: Wed, 30 Sep 2026 23:26:50 +0530 Subject: [PATCH 06/10] docs: clarify anonymous token audience requirement in EXAMPLES.md --- EXAMPLES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index c9a7a1b..54fbac9 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -278,7 +278,7 @@ async def verify_dpop_token(access_token, dpop_proof, http_method, http_url): Anonymous Sessions give a visitor an Auth0 identity before they log in. The access token issued for an anonymous session is a standard Auth0 Bearer JWT, so this SDK validates it like any other token. The one difference is the `sub` claim, which starts with `anon@`. -An anonymous token passes verification by default. To treat anonymous callers differently, or block them, check the `sub` claim after verifying the token. The SDK does not make that authorization decision for you. +An anonymous token is verified like any other. It must be issued for this API's audience. To treat anonymous callers differently, or block them, check the `sub` claim after verifying the token. The SDK does not make that authorization decision for you. ### Serve everyone, branch in the handler From 68342e07dcf9ec8d34e96d0cc4da553125340d2d Mon Sep 17 00:00:00 2001 From: Sourav Basu Date: Wed, 30 Sep 2026 23:30:39 +0530 Subject: [PATCH 07/10] docs: fix imports in anonymous callers snippets in EXAMPLES.md --- EXAMPLES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 54fbac9..85a49d9 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -283,7 +283,6 @@ An anonymous token is verified like any other. It must be issued for this API's ### Serve everyone, branch in the handler ```python -import asyncio from auth0_api_python import ApiClient, ApiClientOptions async def handle_cart(headers): @@ -303,6 +302,8 @@ async def handle_cart(headers): ### Block anonymous callers on a specific route ```python +from auth0_api_python import ApiClient, ApiClientOptions + async def handle_checkout(headers): api_client = ApiClient(ApiClientOptions( domain="your-tenant.auth0.com", @@ -321,6 +322,8 @@ async def handle_checkout(headers): The SDK has no global "reject anonymous" switch. Centralize the check in whatever shared layer your framework uses for auth (middleware, a FastAPI dependency, a decorator). ```python +from auth0_api_python import ApiClient, ApiClientOptions + async def require_logged_in_user(headers): api_client = ApiClient(ApiClientOptions( domain="your-tenant.auth0.com", From 188d68ae489b36922f32fd4926b6e169c2a1bb62 Mon Sep 17 00:00:00 2001 From: Sourav Basu Date: Wed, 30 Sep 2026 23:33:30 +0530 Subject: [PATCH 08/10] docs: add note about ApiClient reuse in anonymous callers examples --- EXAMPLES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/EXAMPLES.md b/EXAMPLES.md index 85a49d9..f1a8dce 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -338,3 +338,6 @@ async def require_logged_in_user(headers): > [!NOTE] > The `anon@` prefix on `sub` is the only signal that distinguishes an anonymous caller from a logged-in user. + +> [!NOTE] +> These snippets construct `ApiClient` inside the handler for clarity. In production, build it once at startup and reuse it, or pass a shared `cache_adapter`, so JWKS and discovery caches persist across requests. From 57b4ae5271d9c2ddd603c3b7c7f2581ccc4bbf74 Mon Sep 17 00:00:00 2001 From: Sourav Basu Date: Wed, 30 Sep 2026 23:40:40 +0530 Subject: [PATCH 09/10] docs: add 403 guidance for anonymous caller blocking in EXAMPLES.md --- EXAMPLES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/EXAMPLES.md b/EXAMPLES.md index f1a8dce..798a8e1 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -341,3 +341,6 @@ async def require_logged_in_user(headers): > [!NOTE] > These snippets construct `ApiClient` inside the handler for clarity. In production, build it once at startup and reuse it, or pass a shared `cache_adapter`, so JWKS and discovery caches persist across requests. + +> [!NOTE] +> Blocking an anonymous caller should return an HTTP `403 Forbidden`. Replace `PermissionError` with your framework's error type. From bffec8dac7f010c7d0908fa502e32ac0e5d47f38 Mon Sep 17 00:00:00 2001 From: Sourav Basu Date: Wed, 30 Sep 2026 23:44:00 +0530 Subject: [PATCH 10/10] docs: move notes inline with their examples in anonymous callers section --- EXAMPLES.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 798a8e1..5a10f4a 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -299,6 +299,9 @@ async def handle_cart(headers): return {"cart": load_user_cart(claims["sub"])} ``` +> [!NOTE] +> These snippets construct `ApiClient` inside the handler for clarity. In production, build it once at startup and reuse it, or pass a shared `cache_adapter`, so JWKS and discovery caches persist across requests. + ### Block anonymous callers on a specific route ```python @@ -317,6 +320,9 @@ async def handle_checkout(headers): return {"order": create_order(claims["sub"])} ``` +> [!NOTE] +> Blocking an anonymous caller should return an HTTP `403 Forbidden`. Replace `PermissionError` with your framework's error type. + ### Block anonymous callers everywhere The SDK has no global "reject anonymous" switch. Centralize the check in whatever shared layer your framework uses for auth (middleware, a FastAPI dependency, a decorator). @@ -338,9 +344,3 @@ async def require_logged_in_user(headers): > [!NOTE] > The `anon@` prefix on `sub` is the only signal that distinguishes an anonymous caller from a logged-in user. - -> [!NOTE] -> These snippets construct `ApiClient` inside the handler for clarity. In production, build it once at startup and reuse it, or pass a shared `cache_adapter`, so JWKS and discovery caches persist across requests. - -> [!NOTE] -> Blocking an anonymous caller should return an HTTP `403 Forbidden`. Replace `PermissionError` with your framework's error type.