Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 162 additions & 18 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
# Auth0 FastAPI-API Examples
# Examples

This document provides examples for using the `auth0-fastapi-api` package to secure your FastAPI applications with Auth0.


- [Bearer Authentication](#bearer-authentication)
- [Scope Validation](#scope-validation)
- [DPoP Authentication](#dpop-authentication)
- [Accept both Bearer and DPoP tokens (default)](#accept-both-bearer-and-dpop-tokens-default)
- [Require only DPoP tokens](#require-only-dpop-tokens)
- [Require only Bearer tokens](#require-only-bearer-tokens)
- [Multiple Custom Domains (MCD)](#multiple-custom-domains-mcd)
- [Static domain list](#static-domain-list)
- [Dynamic resolver function](#dynamic-resolver-function)
- [Hybrid mode](#hybrid-mode)
- [With DPoP](#with-dpop)
- [With custom cache configuration](#with-custom-cache-configuration)
- [Reverse Proxy Support](#reverse-proxy-support)

---

## Bearer Authentication

```python
Expand All @@ -24,12 +41,31 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
http://localhost:8000/api/protected
```

---

## Scope Validation

```python
@app.get("/api/admin")
async def admin_route(claims=Depends(auth0.require_auth(scopes=["admin:access"]))):
return {"message": "Admin access granted"}

@app.delete("/api/resource")
async def delete_route(
claims=Depends(auth0.require_auth(scopes=["delete:data", "admin:access"]))
):
"""Requires BOTH scopes."""
return {"message": "Resource deleted"}
```

---

## DPoP Authentication

> [!NOTE]
> DPoP is in Early Access. Contact Auth0 support to enable it.

**Mixed Mode (default)** - Accept both Bearer and DPoP:
### Accept both Bearer and DPoP tokens (default)

```python
auth0 = Auth0FastAPI(
Expand All @@ -47,7 +83,7 @@ curl -H "Authorization: DPoP YOUR_ACCESS_TOKEN" \
http://localhost:8000/api/protected
```

**DPoP Required Mode** - Reject Bearer tokens:
### Require only DPoP tokens

```python
auth0 = Auth0FastAPI(
Expand All @@ -57,7 +93,7 @@ auth0 = Auth0FastAPI(
)
```

**Bearer-Only Mode** - Disable DPoP:
### Require only Bearer tokens

```python
auth0 = Auth0FastAPI(
Expand All @@ -66,32 +102,140 @@ auth0 = Auth0FastAPI(
dpop_enabled=False
)
```
### Reverse Proxy Support

## Scope Validation
Enable X-Forwarded-* header trust for DPoP behind proxies:

```python
@app.get("/api/admin")
async def admin_route(claims=Depends(auth0.require_auth(scopes=["admin:access"]))):
return {"message": "Admin access granted"}
app = FastAPI()
app.state.trust_proxy = True # Required for load balancers/CDN

@app.delete("/api/resource")
async def delete_route(
claims=Depends(auth0.require_auth(scopes=["delete:data", "admin:access"]))
):
"""Requires BOTH scopes."""
return {"message": "Resource deleted"}
auth0 = Auth0FastAPI(
domain="your-domain.auth0.com",
audience="your-api-identifier"
)
```

## Reverse Proxy Support
---

Enable X-Forwarded-* header trust for DPoP behind proxies:
## Multiple Custom Domains (MCD)

### Static domain list

```python
from fastapi import FastAPI, Depends
from fastapi_plugin.fast_api_client import Auth0FastAPI

app = FastAPI()
app.state.trust_proxy = True # Required for load balancers/CDN
auth0 = Auth0FastAPI(
domains=["tenant1.us.auth0.com", "tenant2.eu.auth0.com"],
audience="your-api-identifier"
)

@app.get("/api/protected")
async def protected_route(claims=Depends(auth0.require_auth())):
return {"user_id": claims["sub"]}
```

```bash
# Token from either domain is accepted
curl -H "Authorization: Bearer TOKEN_FROM_TENANT1" \
http://localhost:8000/api/protected
```

### Dynamic resolver function

```python
from fastapi_plugin import Auth0FastAPI, DomainsResolverContext

def resolve_domains(context: DomainsResolverContext) -> list:
"""Resolve allowed domains based on request context."""
# context['unverified_iss'] - issuer from the token (before verification)
# context.get('request_url') - the API request URL
# context.get('request_headers') - the API request headers
return ["tenant1.us.auth0.com", "auth.example.com"]

auth0 = Auth0FastAPI(
domain="your-domain.auth0.com",
domains=resolve_domains,
audience="your-api-identifier"
)
```

> [!WARNING]
> `DomainsResolver` functions often rely on request headers such as `Host` or `X-Forwarded-Host`. These headers can be spoofed by clients unless your FastAPI instance is behind a trusted proxy and you have a clear trust boundary. Always validate/allowlist hosts and only honor forwarded headers from trusted infrastructure.

### Hybrid mode

Use `domain` and `domains` together for zero-downtime domain migration scenarios:

```python
auth0 = Auth0FastAPI(
domain="primary.us.auth0.com",
domains=["primary.us.auth0.com", "new-domain.example.com"],
audience="your-api-identifier",
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET"
)
```

### With DPoP

```python
auth0 = Auth0FastAPI(
domains=["tenant1.us.auth0.com", "tenant2.eu.auth0.com"],
audience="your-api-identifier",
dpop_enabled=True,
dpop_required=False
)
```

### With custom cache configuration

```python
from fastapi_plugin import Auth0FastAPI

# Option 1: Use default InMemoryCache with custom config (recommended)
auth0 = Auth0FastAPI(
domains=["tenant1.us.auth0.com", "tenant2.eu.auth0.com"],
audience="your-api-identifier",
cache_ttl_seconds=1200, # Cache TTL
cache_max_entries=200 # Max cached entries
)

# Option 2: Provide pre-configured cache adapter
from fastapi_plugin import InMemoryCache

auth0 = Auth0FastAPI(
domains=["tenant1.us.auth0.com", "tenant2.eu.auth0.com"],
audience="your-api-identifier",
cache_adapter=InMemoryCache(max_entries=200), # Configure here
cache_ttl_seconds=1200
# Note: cache_max_entries is ignored when cache_adapter is provided
)

# Option 3: Custom cache implementation (Redis, etc.)
from fastapi_plugin import CacheAdapter

class RedisCache(CacheAdapter):
def __init__(self, redis_client):
self.redis = redis_client

def get(self, key: str):
return self.redis.get(key)

def set(self, key: str, value, ttl_seconds=None):
self.redis.set(key, value, ex=ttl_seconds)

def delete(self, key: str):
self.redis.delete(key)

def clear(self):
self.redis.flushdb()

auth0 = Auth0FastAPI(
domains=["tenant1.us.auth0.com", "tenant2.eu.auth0.com"],
audience="your-api-identifier",
cache_adapter=RedisCache(redis_client),
cache_ttl_seconds=1200
)
```
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,55 @@ asyncio.run(main())

More info https://auth0.com/docs/secure/tokens/token-vault

### 7. Multiple Custom Domains (MCD)

The SDK supports accepting tokens from multiple Auth0 custom domains, enabling multi-tenant applications, zero-downtime domain migrations, and regional deployments.

#### Static Domain List
```python
auth0 = Auth0FastAPI(
domains=["your-tenant.auth0.com", "custom-domain.example.com"],
audience="your-api-identifier"
)
```

#### Dynamic Domain Resolver

**Dynamic Resolver:**
```python
from fastapi_plugin import DomainsResolverContext

def resolve_domains(context: DomainsResolverContext) -> list:
"""Resolve allowed domains based on request context."""
# Access unverified issuer, request URL, and headers
return ["your-tenant.auth0.com", "custom-domain.example.com"]

auth0 = Auth0FastAPI(
domains=resolve_domains,
audience="your-api-identifier"
)
```

**Hybrid Mode:**
```python
auth0 = Auth0FastAPI(
domain="primary.us.auth0.com", # Used for token exchange flows
domains=["primary.us.auth0.com", "new-domain.example.com"], # Both accepted for verification
audience="your-api-identifier",
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET"
)
```

#### Key Features

- **Double Issuer Validation:** Pre-signature and post-signature issuer checks prevent issuer confusion attacks
- **Per-Issuer Caching:** OIDC metadata and JWKS are cached separately for each domain with configurable TTL and LRU eviction
- **DPoP Compatible:** Full DPoP support works seamlessly across multiple domains
- **Custom Cache Backends:** Plug in Redis, Memcached, or other cache implementations via the `CacheAdapter` interface

πŸ“– **For detailed examples including dynamic resolvers, cache configuration, and DPoP integration, see the [Multiple Custom Domains section in EXAMPLES.md](EXAMPLES.md#multiple-custom-domains-mcd).**

## Feedback

### Contributing
Expand Down
18 changes: 17 additions & 1 deletion fastapi_plugin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
from .fast_api_client import Auth0FastAPI
from auth0_api_python import (
CacheAdapter,
ConfigurationError,
DomainsResolver,
DomainsResolverContext,
DomainsResolverError,
InMemoryCache,
)

__all__ = ["Auth0FastAPI"]
__all__ = [
"Auth0FastAPI",
"CacheAdapter",
"ConfigurationError",
"DomainsResolver",
"DomainsResolverContext",
"DomainsResolverError",
"InMemoryCache",
]
Loading
Loading