This guide demonstrates how to integrate Auth0 with any new or existing Python API built with Flask.
1
Create a new Flask project
Create a new directory for your Flask API:
mkdir flask-auth0-apicd flask-auth0-api
Create a virtual environment and activate it:
python -m venv venvsource venv/bin/activate # On Windows: venv\Scripts\activate
2
Install dependencies
Create a requirements.txt file with the following dependencies:
requirements.txt
flask>=3.0auth0-api-pythonpython-dotenv
Install the dependencies:
pip install -r requirements.txt
3
Setup your Auth0 API
Next up, you need to create a new API on your Auth0 tenant and configure your application.You can choose to do this automatically by running a CLI command or do it manually via the Dashboard:
Identifier: https://my-flask-api (this will be your audience)
Signing Algorithm: RS256
Click Create
Copy your Domain from the Dashboard (found under Applications → Applications → [Your App] → Settings)
Copy the Identifier you just created (this is your audience)
Your Domain should not include https:// - use only the domain name (e.g., your-tenant.auth0.com).The Audience (API Identifier) is a unique identifier for your API and can be any valid URI.
Run the following shell command on your project’s root directory to create an Auth0 API and generate a .env file:
Permissions define what actions can be performed on your API. You can add multiple permissions like write:messages, delete:messages, etc. The /api/private-scoped endpoint in this quickstart requires the read:messages permission.
5
Configure the Auth0 client
If you used the CLI method in Step 3, your .env file was automatically created. Skip to creating the app.py file below.
If you used the Dashboard method, create a .env file in your project root to store your Auth0 configuration:
The verify_request() method automatically detects whether the request uses Bearer or DPoP authentication. When DPoP is used, it validates both the access token and the DPoP proof according to RFC 9449.
Scope-Based Authorization
Create a decorator to check for specific scopes:
def require_scope(required_scope): def decorator(f): @wraps(f) async def wrapper(*args, **kwargs): if not hasattr(g, "user_claims"): return jsonify({"error": "Unauthorized"}), 401 scopes = g.user_claims.get("scope", "").split() if required_scope not in scopes: return jsonify({"error": "Insufficient permissions"}), 403 return await f(*args, **kwargs) return wrapper return decorator@app.route("/api/admin")@require_auth@require_scope("admin:write")async def admin_endpoint(): return jsonify({"message": "Admin access granted"})
Error Handling Best Practices
Implement comprehensive error handling with specific error types:
All authentication errors extend from BaseAuthError, which provides methods like get_status_code(), get_headers(), and get_error_code() for proper HTTP responses with WWW-Authenticate headers.
Using Before-Request Middleware
For applications where most endpoints require authentication, use Flask’s before_request to validate tokens globally:
from flask import gPUBLIC_ROUTES = ["/api/public", "/health"]@app.before_requestasync def verify_token(): # Skip auth for public routes if request.path in PUBLIC_ROUTES: return auth_header = request.headers.get("Authorization", "") if not auth_header.startswith("Bearer "): return jsonify({"error": "Missing authorization"}), 401 token = auth_header.split(" ")[1] try: claims = await api_client.verify_access_token(token) g.user_claims = claims except BaseAuthError as e: return jsonify({"error": str(e)}), e.get_status_code(), e.get_headers()@app.route("/api/protected-data")async def protected_data(): # Claims automatically available via g.user_claims return jsonify({"user_id": g.user_claims["sub"]})
Symptom: Getting 401 errors even with valid-looking tokensCause: The audience in your token doesn’t match the audience configured in your API clientSolution:
Verify the AUTH0_AUDIENCE in your .env file matches your Auth0 API Identifier exactly
The audience is case-sensitive
Ensure the audience is a URL or URN format (e.g., https://my-api not my-api)
401 Unauthorized - Invalid issuer
Symptom: Token validation fails with issuer mismatchCause: The domain configuration doesn’t match the token issuerSolution:
Verify AUTH0_DOMAIN is correct (e.g., tenant.us.auth0.com)
Don’t include https:// in the domain
Don’t include a trailing slash
Configuration values not found
Symptom: None values or environment variable errorsCause: Environment variables not loaded or .env file not foundSolution:
Ensure .env file exists in your project root
Verify load_dotenv() is called before accessing os.getenv()
Check that variable names match exactly (case-sensitive)
Flask async support errors
Symptom: RuntimeError: This event loop is already running or similar async errorsCause: Using async routes without Flask 3.0+ or mixing sync/async incorrectlySolution:
Upgrade to Flask 3.0 or higher: pip install --upgrade flask
Ensure all route handlers using api_client are declared as async def
Don’t use asyncio.run() within route handlers
Token expired errors
Symptom: VerifyAccessTokenError: Token is expiredCause: The access token has passed its expiration timeSolution:
Request a new token from the Auth0 Dashboard Test tab
Implement token refresh in your client application
Tokens from the Dashboard are typically valid for 24 hours
Missing Authorization header
Symptom: Missing or invalid authorization header errorCause: Request doesn’t include the Authorization header or uses incorrect formatSolution:
Ensure the header is named Authorization (capital A)