Use when securing FastAPI API endpoints with JWT Bearer token validation, scope/permission checks, or stateless auth - integrates auth0-fastapi-api for REST...
---
name: auth0-fastapi-api
description: "Use when securing FastAPI API endpoints with JWT Bearer token validation, scope/permission checks, or stateless auth - integrates auth0-fastapi-api for REST APIs receiving access tokens from SPAs, mobile apps, or other clients. Also handles DPoP proof-of-possession token binding. Triggers on: Auth0FastAPI, FastAPI API auth, JWT validation, require_auth, DPoP."
license: Apache-2.0
metadata:
author: Auth0 <support@auth0.com>
version: '1.0.1'
openclaw:
emoji: "\U0001F510"
homepage: https://github.com/auth0/agent-skills
---
# Auth0 FastAPI API Integration
Protect FastAPI API endpoints with JWT access token validation using `auth0-fastapi-api`.
> **Note:** This SDK is currently in beta. The API surface may change before the stable 1.0 release. Check [PyPI](https://pypi.org/project/auth0-fastapi-api/) for the latest version. Requires Python >= 3.9 and FastAPI >= 0.115.11.
---
## Prerequisites
- FastAPI application (Python 3.9+)
- Auth0 API resource configured (not an Application — must be an API)
- If you don't have Auth0 set up yet, use the `auth0-quickstart` skill first
## When NOT to Use
- **Server-rendered web applications** — Use a session-based login/logout flow instead
- **Single Page Applications** — Use `auth0-react`, `auth0-vue`, or `auth0-angular` for client-side auth
- **Mobile applications** — Use `auth0-react-native` or `auth0-android`
- **Issuing tokens** — This skill is for *validating* access tokens, not issuing them
---
## Quick Start Workflow
### 1. Install SDK
```bash
pip install auth0-fastapi-api python-dotenv
```
### 2. Create Auth0 API
You need an **API** (not Application) in Auth0.
> **STOP — ask the user before proceeding.**
>
> Ask exactly this question and wait for their answer before doing anything else:
>
> > "How would you like to create the Auth0 API resource?
> > 1. **Automated** — I'll run Auth0 CLI scripts that create the resource and write the exact values to your `.env` automatically.
> > 2. **Manual** — You create the API yourself in the Auth0 Dashboard (or via `auth0 apis create`) and provide me the Domain and Audience.
> >
> > Which do you prefer? (1 = Automated / 2 = Manual)"
>
> Do NOT proceed to any setup steps until the user has answered. Do NOT default to manual.
**If the user chose Automated**, follow the [Setup Guide](references/setup.md) for complete CLI scripts. The automated path writes `.env` for you — skip Step 3 below and proceed directly to Step 4.
**If the user chose Manual**, follow the [Setup Guide](references/setup.md) (Manual Setup section) for full instructions. Then continue with Step 3 below.
Quick reference for manual API creation:
```bash
# Using Auth0 CLI
auth0 apis create \
--name "My FastAPI API" \
--identifier https://my-api.example.com
```
Or create manually in Auth0 Dashboard → Applications → APIs
### 3. Configure Environment
Create `.env`:
```bash
AUTH0_DOMAIN=your-tenant.us.auth0.com
AUTH0_AUDIENCE=https://your-api.example.com
```
`AUTH0_DOMAIN` is your Auth0 tenant domain (without `https://`). `AUTH0_AUDIENCE` is the API identifier you set when creating the API resource in Auth0.
### 4. Initialize Auth0
```python
import os
from fastapi import FastAPI, Depends
from fastapi_plugin import Auth0FastAPI
from dotenv import load_dotenv
load_dotenv()
app = FastAPI()
auth0 = Auth0FastAPI(
domain=os.getenv("AUTH0_DOMAIN"),
audience=os.getenv("AUTH0_AUDIENCE"),
)
```
Create one `Auth0FastAPI` instance per application and reuse it across routes. Never hardcode the domain or audience — always use environment variables.
### 5. Protect Routes
```python
# Require any valid access token
@app.get("/api/private")
async def private(claims: dict = Depends(auth0.require_auth())):
return {"user": claims["sub"]}
# No authentication required
@app.get("/api/public")
async def public():
return {"message": "Public endpoint"}
```
The `require_auth()` dependency validates the Bearer token, verifies the issuer and audience, and returns the decoded JWT claims.
Error responses:
- **400** `invalid_request` — Missing or malformed Authorization header
- **401** `invalid_token` — Expired token, invalid signature, wrong issuer/audience
- **403** `insufficient_scope` — Valid token but missing required scopes
- **500** `internal_server_error` — Unexpected errors
Response body format: `{"detail": {"error": "...", "error_description": "..."}}`
### 6. Protect Routes with Scope Checks
```python
# Requires the read:messages scope
@app.get("/api/messages")
async def get_messages(claims: dict = Depends(auth0.require_auth(scopes="read:messages"))):
return {"messages": []}
# Requires both read:data and write:data scopes
@app.post("/api/data")
async def write_data(claims: dict = Depends(auth0.require_auth(scopes=["read:data", "write:data"]))):
return {"created": True}
```
`require_auth(scopes=...)` checks the `scope` claim in the JWT. All specified scopes must be present (AND logic). Missing scopes return **403**.
### 7. Access Token Claims
The decoded JWT claims are returned directly from the dependency:
```python
@app.get("/api/profile")
async def profile(claims: dict = Depends(auth0.require_auth())):
return {
"sub": claims["sub"], # user ID
"scope": claims.get("scope"), # granted scopes
}
```
Key claims:
- `claims["sub"]` — user/client ID
- `claims["scope"]` — space-separated granted scopes
- `claims["iss"]` — issuer (your Auth0 domain URL)
- `claims["aud"]` — audience
- `claims["exp"]` — expiration timestamp
- `claims["iat"]` — issued-at timestamp
### 8. Protect Routes Without Needing Claims
```python
@app.get("/api/protected", dependencies=[Depends(auth0.require_auth())])
async def protected():
return {"message": "You need a valid access token to see this."}
```
### 9. Test the API
```bash
# No token — expect 401
curl http://localhost:8000/api/private
# With a valid access token
curl http://localhost:8000/api/private \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
Get a test token via Client Credentials flow or Auth0 Dashboard → APIs → Test tab.
---
## Common Mistakes
| Mistake | Fix |
|---------|-----|
| Hardcoding `domain` or `audience` in source | Always read from environment variables — never embed credentials in code |
| Using `python-jose` or `PyJWT` directly | Not needed; `auth0-fastapi-api` handles all validation via JWKS |
| Manually parsing `Authorization` header | The SDK extracts and validates the token automatically |
| Calling `jwt.decode()` manually | The SDK verifies tokens against the JWKS endpoint — do not verify yourself |
| Using `fastapi-users` for Auth0 JWT validation | That package is for user management, not Auth0 JWT verification |
| Created an Application instead of an API in Auth0 | Must create an **API** resource (Applications → APIs) — an Application doesn't issue access tokens with the right audience |
| Passing `domain` as full URL with `https://` | `domain` should be the bare domain, e.g. `my-tenant.us.auth0.com`, not `https://my-tenant.us.auth0.com` |
| Using an ID token instead of an access token | Must use the **access token** for API auth — ID tokens are for the client app, not for API authorization |
| Not configuring CORS for SPA clients | Add `CORSMiddleware` to allow requests from your frontend origin |
| `os.getenv()` returns `None` silently | Ensure `python-dotenv` is installed and `load_dotenv()` is called before `Auth0FastAPI()` initialization — or use `os.environ[]` to fail fast |
---
## DPoP Support
Built-in proof-of-possession token binding per RFC 9449. DPoP is enabled by default in mixed mode (accepts both Bearer and DPoP tokens). See [Integration Guide](references/integration.md#dpop-support) for configuration.
---
## Related Skills
- `auth0-quickstart` - Basic Auth0 setup and framework detection
- `auth0-mfa` - Add Multi-Factor Authentication
- `auth0-cli` - Manage Auth0 resources from the terminal
---
## Quick Reference
**Auth0FastAPI configuration:**
```python
auth0 = Auth0FastAPI(
domain=os.getenv("AUTH0_DOMAIN"), # required (or use domains)
audience=os.getenv("AUTH0_AUDIENCE"), # required
dpop_enabled=True, # default; set False for Bearer-only
dpop_required=False, # default; set True to reject Bearer tokens
)
```
**Route protection:**
```python
Depends(auth0.require_auth()) # any valid token
Depends(auth0.require_auth(scopes="read:res")) # single scope
Depends(auth0.require_auth(scopes=["r", "w"])) # all scopes required
```
**Accessing claims:**
```python
claims["sub"] # user/client ID
claims["scope"] # space-separated scopes
```
**Environment variables:**
- `AUTH0_DOMAIN` — your Auth0 tenant domain (e.g. `tenant.us.auth0.com`)
- `AUTH0_AUDIENCE` — your API identifier (e.g. `https://api.example.com`)
**Common Use Cases:**
- Protect routes → `Depends(auth0.require_auth())` (see Step 5)
- Scope enforcement → `Depends(auth0.require_auth(scopes="..."))` (see Step 6)
- DPoP token binding → [Integration Guide](references/integration.md#dpop-support)
- Reverse proxy setup → [Integration Guide](references/integration.md#reverse-proxy-support)
- Advanced configuration → [API Reference](references/api.md)
---
## Detailed Documentation
- **[Setup Guide](references/setup.md)** — Auth0 CLI setup, environment configuration, getting test tokens
- **[Integration Guide](references/integration.md)** — DPoP, scopes, error handling, reverse proxy, testing
- **[API Reference](references/api.md)** — Complete constructor options, method signatures, error codes
---
## References
- [auth0-fastapi-api GitHub](https://github.com/auth0/auth0-fastapi-api)
- [auth0-fastapi-api on PyPI](https://pypi.org/project/auth0-fastapi-api/)
- [Auth0 FastAPI API Quickstart](https://auth0.com/docs/quickstart/backend/fastapi)
- [FastAPI Dependency Injection](https://fastapi.tiangolo.com/tutorial/dependencies/)
- [Access Tokens Guide](https://auth0.com/docs/secure/tokens/access-tokens)
don't have the plugin yet? install it then click "run inline in claude" again.
use this skill when you need to protect FastAPI API endpoints with JWT access token validation from Auth0. deploy this for REST APIs receiving tokens from SPAs, mobile apps, or other clients that have already obtained access tokens via an Auth0 authorization flow. scope this to stateless token validation only, not token issuance or user login flows. handles standard Bearer token auth plus optional DPoP proof-of-possession token binding per RFC 9449.
environment variables (required):
AUTH0_DOMAIN: your Auth0 tenant domain without https://, e.g. tenant.us.auth0.comAUTH0_AUDIENCE: your Auth0 API resource identifier, e.g. https://my-api.example.comauth0 api resource (required):
auth0 apis create --name "My FastAPI API" --identifier https://my-api.example.compython dependencies (required):
fastapi >= 0.115.11auth0-fastapi-api >= 1.0.1 (currently beta, API surface may change)python-dotenv (for local .env loading)external connection:
https://{AUTH0_DOMAIN}/.well-known/jwks.json)install dependencies: run pip install auth0-fastapi-api python-dotenv to get the SDK and environment loader. verify installation with python -c "import auth0_fastapi_api".
output: no console output on success, or package installation error if pip fails.
create or confirm auth0 api resource: ask the user this exact question and wait for their answer before proceeding: "how would you like to create the auth0 api resource? 1. automated (i'll run auth0 cli scripts that create the resource and write exact values to your .env automatically). 2. manual (you create the api yourself in the auth0 dashboard or via auth0 apis create and provide me the domain and audience). which do you prefer? (1 = automated / 2 = manual)". do not default to manual. do not proceed until the user answers.
input: user choice (1 or 2). output: if 1, automated setup scripts are provided; if 2, user is directed to manual setup instructions.
if automated (choice 1): execute auth0 cli commands to create the api resource and write AUTH0_DOMAIN and AUTH0_AUDIENCE to .env file automatically. user does not manually enter env vars.
input: auth0 cli installed and authenticated.
output: .env file written with AUTH0_DOMAIN and AUTH0_AUDIENCE populated.
if manual (choice 2): user creates the api resource themselves and provides you the domain (e.g. tenant.us.auth0.com) and audience (e.g. https://my-api.example.com). create .env file with these values:
AUTH0_DOMAIN=<user-provided-domain>
AUTH0_AUDIENCE=<user-provided-audience>
input: user-provided domain and audience strings.
output: .env file created with auth0 config.
initialize auth0fastapi instance: in your fastapi app, load environment variables and create a single reusable Auth0FastAPI instance:
import os
from fastapi import FastAPI, Depends
from auth0_fastapi_api import Auth0FastAPI
from dotenv import load_dotenv
load_dotenv()
app = FastAPI()
auth0 = Auth0FastAPI(
domain=os.getenv("AUTH0_DOMAIN"),
audience=os.getenv("AUTH0_AUDIENCE"),
)
input: .env file exists with AUTH0_DOMAIN and AUTH0_AUDIENCE set.
output: auth0 instance ready to use in route handlers; os.getenv() returns non-None values.
protect routes with token validation: add Depends(auth0.require_auth()) to route handlers that require a valid bearer token. the sdk automatically extracts the token from the authorization: bearer <token> header, validates signature and claims against the jwks endpoint, and returns decoded jwt claims as a dict.
@app.get("/api/private")
async def private(claims: dict = Depends(auth0.require_auth())):
return {"user": claims["sub"]}
input: incoming http request with authorization: bearer <access_token> header.
output: if token is valid, claims dict is passed to handler; if invalid, sdk returns http error (see output contract).
enforce scopes (optional): if a route requires specific oauth scopes, pass them to require_auth(scopes=...). pass a single scope as a string or multiple scopes as a list. all scopes must be present in the token (and logic).
@app.get("/api/messages")
async def get_messages(claims: dict = Depends(auth0.require_auth(scopes="read:messages"))):
return {"messages": []}
@app.post("/api/data")
async def write_data(claims: dict = Depends(auth0.require_auth(scopes=["read:data", "write:data"]))):
return {"created": True}
input: jwt token with scope claim (space-separated scope string).
output: if all required scopes present, claims passed to handler; if missing scopes, sdk returns 403 insufficient_scope error.
access decoded claims in handlers: the claims dict returned by require_auth() contains standard jwt claims:
claims["sub"]: user or client idclaims["scope"]: space-separated granted scopesclaims["iss"]: issuer url (your auth0 domain)claims["aud"]: audience (your api identifier)claims["exp"]: token expiration timestamp (unix seconds)claims["iat"]: token issued-at timestamp (unix seconds)input: decoded jwt claims dict from sdk. output: handler accesses specific claims and uses them for business logic.
protect routes without needing claims: if a route just needs to verify a valid token exists but doesn't use the claims, use dependencies=[Depends(auth0.require_auth())] on the route decorator:
@app.get("/api/protected", dependencies=[Depends(auth0.require_auth())])
async def protected():
return {"message": "You need a valid access token to see this."}
input: incoming http request with bearer token. output: token validated; if valid, route handler executes; if invalid, 401 or 400 returned.
enable cors for spa clients (if needed): if your api receives requests from a web frontend on a different origin, add fastapi cors middleware:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://example.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
input: list of allowed frontend origins. output: api responds with correct cors headers so browser preflight and actual requests succeed.
test the api: obtain a test access token (via client credentials flow or auth0 dashboard apis test tab), then send requests:
curl http://localhost:8000/api/private
# expect 400 or 401
curl http://localhost:8000/api/private \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# expect 200 with claims
input: valid access token string. output: if token valid, 200 response with json body; if missing or invalid, 400/401/403 error with json detail.
(optional) enable dpop token binding: set dpop_enabled=True (default) in Auth0FastAPI() to accept both bearer and dpop tokens. set dpop_required=True to reject plain bearer tokens and require dpop only:
auth0 = Auth0FastAPI(
domain=os.getenv("AUTH0_DOMAIN"),
audience=os.getenv("AUTH0_AUDIENCE"),
dpop_enabled=True,
dpop_required=False,
)
input: dpop configuration flags.
output: sdk validates dpop proofs on incoming requests that include dpop headers; bearer-only requests accepted if dpop_required=False.
if user chooses automated setup (step 2, choice 1): run auth0 cli commands to provision the api resource and write .env. skip manual config and move to step 5.
if user chooses manual setup (step 2, choice 2): user creates the api resource themselves and provides domain/audience. you write .env manually and proceed to step 5.
if os.getenv("AUTH0_DOMAIN") returns None: .env file not loaded or missing AUTH0_DOMAIN key. ensure load_dotenv() is called before Auth0FastAPI() initialization, or substitute os.environ[] (which raises KeyError and fails fast instead of silently returning None).
if incoming request has no authorization header: sdk returns 400 invalid_request with error detail.
if token is expired, malformed, or has invalid signature: sdk returns 401 invalid_token with error detail.
if token is valid but issuer or audience doesn't match: sdk returns 401 invalid_token with error detail.
if token is valid but required scopes are missing: sdk returns 403 insufficient_scope with error detail.
if dpop_required=True but request uses plain bearer token: sdk returns 401 error (dpop proof required).
if jwks endpoint is unreachable or returns error: sdk caches keys and falls back to cached set. if no cache, sdk raises exception and request fails (network error handling depends on fastapi error middleware).
if token is valid for a different audience (wrong api resource): sdk returns 401 invalid_token. confirm user is requesting token for the correct api identifier in auth0.
if using a hardcoded domain or audience in source: this violates the inputs contract. always read from environment variables to avoid embedding credentials.
on successful token validation:
sub, scope, iss, aud, exp, iaton missing or malformed authorization header:
{"detail": {"error": "invalid_request", "error_description": "..."}}on expired, invalid, or forged token:
{"detail": {"error": "invalid_token", "error_description": "..."}}on missing required scopes:
{"detail": {"error": "insufficient_scope", "error_description": "..."}}on unexpected error (e.g. jwks fetch failure):
{"detail": {"error": "internal_server_error", "error_description": "..."}}environment configuration:
.env file location: project root or directory where load_dotenv() is calledKEY=value (one per line, no quotes needed unless value contains spaces)python module:
Auth0FastAPI class instantiated and reused across all routes in the appauthorization: bearer <token> and receive a 200 response with the route handler's json output (including claims data).insufficient_scope response.invalid_token.| mistake | fix |
|---|---|
hardcoding domain or audience in source code |
always read from environment variables. never embed credentials. use os.getenv() or os.environ[]. |
manually parsing the authorization header or calling jwt.decode() directly |
the sdk handles header extraction and token validation against jwks. do not verify tokens yourself. |
using python-jose or pyjwt to validate tokens |
not needed. the sdk handles all validation via jwks endpoint. |
| creating an auth0 application instead of an api resource | must be an api resource (dashboard → applications → apis). applications don't issue access tokens with the right audience. |
passing domain as a full url with https:// |
domain should be the bare domain, e.g. tenant.us.auth0.com, not https://tenant.us.auth0.com. |
| using an id token instead of an access token | api auth requires the access token, not the id token. id tokens are for the client app, not api authorization. |
| not configuring cors for spa clients | add CORSMiddleware if your frontend is on a different origin. |
relying on os.getenv() without checking for None |
if .env is not loaded or key is missing, os.getenv() returns None silently. use os.environ[] to fail fast, or check the result explicitly. |
using fastapi-users for auth0 jwt validation |
that package is for user management, not auth0 jwt token verification. use auth0-fastapi-api instead. |
calling require_auth() multiple times per route |
create a single auth0 instance and reuse it. calling it multiple times is redundant and slower. |
| skipping scope checks on sensitive routes | always enforce scopes for endpoints that mutate data or expose sensitive info. |
| not testing with expired tokens | token expiration (the exp claim) is validated by the sdk. verify that expired tokens are rejected with 401. |
dpop (proof-of-possession) support is enabled by default and compliant with rfc 9449. dpop binds access tokens to a client's public key, preventing token reuse if intercepted. configure via:
auth0 = Auth0FastAPI(
domain=os.getenv("AUTH0_DOMAIN"),
audience=os.getenv("AUTH0_AUDIENCE"),
dpop_enabled=True, # accept bearer or dpop (default)
dpop_required=False, # set true to reject plain bearer tokens
)
if dpop_required=True, only requests with valid dpop proofs are accepted. clients must send the dpop header with a signed jwt. this prevents token theft but is stricter. use only if your clients support it.
see the integration guide (references/integration.md#dpop-support) for dpop client implementation examples.
auth0-quickstart: basic auth0 setup and framework detection.auth0-mfa: add multi-factor authentication to your app.auth0-cli: manage auth0 resources from the terminal.auth0fastapi configuration:
auth0 = Auth0FastAPI(
domain=os.getenv("AUTH0_DOMAIN"), # required
audience=os.getenv("AUTH0_AUDIENCE"), # required
dpop_enabled=True, # default; set False for bearer-only
dpop_required=False, # default; set True to reject bearer tokens
)
route protection:
Depends(auth0.require_auth()) # any valid token
Depends(auth0.require_auth(scopes="read:resource")) # single scope
Depends(auth0.require_auth(scopes=["r", "w"])) # multiple scopes (and logic)
accessing claims:
claims["sub"] # user or client id
claims["scope"] # space-separated scopes
claims["iss"] # issuer (auth0 domain url)
claims["aud"] # audience (api identifier)
claims["exp"] # expiration timestamp
claims["iat"] # issued-at timestamp
environment variables:
AUTH0_DOMAIN: your auth0 tenant domain (e.g. tenant.us.auth0.com)AUTH0_AUDIENCE: your api identifier (e.g. https://api.example.com)common patterns:
Depends(auth0.require_auth()) (see step 6)Depends(auth0.require_auth(scopes="...")) (see step 7)dpop_required=True (see advanced options)CORSMiddleware (see step 10)