Skip to main content
Prerequisites: Before you begin, ensure you have the following installed:
  • Python and pip
  • Your preferred code editor
This quickstart works with the auth0-python-api-samples repository.

Get Started

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-api
cd flask-auth0-api
Create a virtual environment and activate it:
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
2

Install dependencies

Create a requirements.txt file with the following dependencies:
requirements.txt
flask==2.3.3
python-dotenv
pyjwt
flask-cors
six
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.Alternatively, you can read our getting started guide that helps you set up your first API through the Auth0 dashboard.You can do this manually via the Dashboard or use the Auth0 CLI:
  1. Go to the Auth0 DashboardApplicationsAPIs
  2. Click Create API
  3. Enter your API details:
    • Name: My Flask API
    • Identifier: https://my-flask-api (this will be your audience)
    • Signing Algorithm: RS256
  4. Click Create
  5. Copy your Domain from the Dashboard (found under ApplicationsApplications[Your App]Settings)
  6. 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.
4

Define API permissions

Configure permissions (scopes) for your API to control access to specific resources:
  1. In the Auth0 Dashboard, navigate to ApplicationsAPIs
  2. Select your API (My Flask API)
  3. Go to the Permissions tab
  4. Click Add Permission
  5. Add the following permission:
    • Permission (Scope): read:messages
    • Description: Read messages
  6. Click Add
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:
.env
AUTH0_DOMAIN=your-tenant.us.auth0.com
API_IDENTIFIER=https://my-flask-api
Replace your-tenant.us.auth0.com with your actual Auth0 domain and update the API_IDENTIFIER to match your API identifier from the dashboard.
Create a server.py file and import the required dependencies:
server.py
from functools import wraps
import json
from os import environ as env
from typing import Dict

from six.moves.urllib.request import urlopen
from dotenv import load_dotenv, find_dotenv
from flask import Flask, request, jsonify, _request_ctx_stack, Response
from flask_cors import cross_origin
import jwt

# Load environment variables
ENV_FILE = find_dotenv()
if ENV_FILE:
    load_dotenv(ENV_FILE)

AUTH0_DOMAIN = env.get("AUTH0_DOMAIN")
API_IDENTIFIER = env.get("API_IDENTIFIER")
ALGORITHMS = ["RS256"]
APP = Flask(__name__)


# Error handler
class AuthError(Exception):
    """An AuthError is raised whenever the authentication failed."""
    def __init__(self, error: Dict[str, str], status_code: int):
        super().__init__()
        self.error = error
        self.status_code = status_code


@APP.errorhandler(AuthError)
def handle_auth_error(ex: AuthError) -> Response:
    """Serializes the given AuthError as json and sets the response status code accordingly."""
    response = jsonify(ex.error)
    response.status_code = ex.status_code
    return response
6

Create authentication functions

Add functions to extract and validate the access token:
server.py
def get_token_auth_header() -> str:
    """Obtains the access token from the Authorization Header"""
    auth = request.headers.get("Authorization", None)
    if not auth:
        raise AuthError({"code": "authorization_header_missing",
                        "description": "Authorization header is expected"}, 401)

    parts = auth.split()

    if parts[0].lower() != "bearer":
        raise AuthError({"code": "invalid_header",
                        "description": "Authorization header must start with Bearer"}, 401)
    if len(parts) == 1:
        raise AuthError({"code": "invalid_header",
                        "description": "Token not found"}, 401)
    if len(parts) > 2:
        raise AuthError({"code": "invalid_header",
                        "description": "Authorization header must be Bearer token"}, 401)

    token = parts[1]
    return token


def requires_scope(required_scope: str) -> bool:
    """Determines if the required scope is present in the access token"""
    token = get_token_auth_header()
    unverified_claims = jwt.decode(token, options={"verify_signature": False})
    if unverified_claims.get("scope"):
        token_scopes = unverified_claims["scope"].split()
        for token_scope in token_scopes:
            if token_scope == required_scope:
                return True
    return False


def requires_auth(func):
    """Determines if the access token is valid"""
    @wraps(func)
    def decorated(*args, **kwargs):
        token = get_token_auth_header()
        jsonurl = urlopen("https://" + AUTH0_DOMAIN + "/.well-known/jwks.json")
        jwks = json.loads(jsonurl.read())
        try:
            unverified_header = jwt.get_unverified_header(token)
        except jwt.PyJWTError as jwt_error:
            raise AuthError({"code": "invalid_header",
                            "description": "Invalid header. Use an RS256 signed JWT Access Token"}, 401) from jwt_error
        
        if unverified_header["alg"] == "HS256":
            raise AuthError({"code": "invalid_header",
                            "description": "Invalid header. Use an RS256 signed JWT Access Token"}, 401)
        
        public_key = None
        for jwk in jwks["keys"]:
            if jwk["kid"] == unverified_header["kid"]:
                public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk))
        
        if public_key:
            try:
                payload = jwt.decode(
                    token,
                    public_key,
                    algorithms=ALGORITHMS,
                    audience=API_IDENTIFIER,
                    issuer="https://" + AUTH0_DOMAIN + "/"
                )
            except jwt.ExpiredSignatureError as expired_sign_error:
                raise AuthError({"code": "token_expired",
                                "description": "token is expired"}, 401) from expired_sign_error
            except jwt.InvalidAudienceError as jwt_audience_error:
                raise AuthError({"code": "invalid_audience",
                                "description": "incorrect audience, please check the audience"}, 401) from jwt_audience_error
            except jwt.InvalidIssuerError as jwt_issuer_error:
                raise AuthError({"code": "invalid_issuer",
                                "description": "incorrect issuer, please check the issuer"}, 401) from jwt_issuer_error
            except Exception as exc:
                raise AuthError({"code": "invalid_header",
                                "description": "Unable to parse authentication token."}, 401) from exc

            _request_ctx_stack.top.current_user = payload
            return func(*args, **kwargs)
        
        raise AuthError({"code": "invalid_header",
                        "description": "Unable to find appropriate key"}, 401)

    return decorated
7

Create API endpoints

Add the public and protected endpoints:
server.py
# Controllers API
@APP.route("/api/public")
@cross_origin(headers=["Content-Type", "Authorization"])
def public():
    """No access token required to access this route"""
    response = "Hello from a public endpoint! You don't need to be authenticated to see this."
    return jsonify(message=response)


@APP.route("/api/private")
@cross_origin(headers=["Content-Type", "Authorization"])
@requires_auth
def private():
    """A valid access token is required to access this route"""
    response = "Hello from a private endpoint! You need to be authenticated to see this."
    return jsonify(message=response)


@APP.route("/api/private-scoped")
@cross_origin(headers=["Content-Type", "Authorization"])
@requires_auth
def private_scoped():
    """A valid access token and an appropriate scope are required to access this route"""
    if requires_scope("read:messages"):
        response = "Hello from a private endpoint! You need to be authenticated and have a scope of read:messages to see this."
        return jsonify(message=response)
    raise AuthError({
        "code": "Unauthorized",
        "description": "You don't have access to this resource"
    }, 403)


if __name__ == "__main__":
    APP.run(host="0.0.0.0", port=env.get("PORT", 3010))
8

Run your API

Start your Flask application:
python server.py
Your API is now running (check your console output for the exact URL, typically http://localhost:3010).
CheckpointYou should now have a fully functional Auth0-protected Flask API running on your localhost with three endpoints:
  • GET /api/public - Accessible without authentication
  • GET /api/private - Requires a valid Auth0 access token
  • GET /api/private-scoped - Requires authentication and the read:messages scope

Test Your API

To test your protected endpoints, you need an access token.

Get a test token

  1. Go to the Auth0 Dashboard
  2. Navigate to Applications → APIs
  3. Select your API
  4. Go to the Test tab
  5. Copy the access token
For testing the scoped endpoint (/api/private-scoped), you’ll need to define the read:messages permission in your API settings under the Permissions tab, and ensure it’s granted in your access token.

Make a request

Test the public endpoint (no token required):
curl http://localhost:3010/api/public
Test the protected endpoint (token required):
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  http://localhost:3010/api/private
Replace YOUR_ACCESS_TOKEN with the token you copied from the Auth0 Dashboard.

Advanced Usage

Customize the AuthError handler to provide more detailed error responses:
@APP.errorhandler(AuthError)
def handle_auth_error(ex: AuthError) -> Response:
    """Enhanced error handler with logging"""
    import logging
    logger = logging.getLogger(__name__)
    logger.warning(f"Auth error: {ex.error}")
    
    response = jsonify(ex.error)
    response.status_code = ex.status_code
    response.headers['WWW-Authenticate'] = f'Bearer realm="api", error="{ex.error.get("code")}"'
    return response
Manage different configurations for development and production:
import os

class Config:
    """Base configuration"""
    AUTH0_DOMAIN = env.get("AUTH0_DOMAIN")
    API_IDENTIFIER = env.get("API_IDENTIFIER")
    ALGORITHMS = ["RS256"]

class DevelopmentConfig(Config):
    """Development configuration"""
    DEBUG = True
    TESTING = False

class ProductionConfig(Config):
    """Production configuration"""
    DEBUG = False
    TESTING = False

# Select config based on environment
config = {
    'development': DevelopmentConfig,
    'production': ProductionConfig,
    'default': DevelopmentConfig
}

env_name = env.get('FLASK_ENV', 'default')
app_config = config[env_name]
After successful authentication, access user information from the token:
@APP.route("/api/user-info")
@cross_origin(headers=["Content-Type", "Authorization"])
@requires_auth
def user_info():
    """Returns user information from the access token"""
    current_user = _request_ctx_stack.top.current_user
    
    return jsonify({
        "user_id": current_user.get("sub"),
        "permissions": current_user.get("permissions", []),
        "scope": current_user.get("scope", "")
    })
Deploy your API using Docker (based on the sample repository):Create a Dockerfile:
FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY server.py .
COPY .env .

EXPOSE 3010

CMD ["python", "server.py"]
Build and run:
docker build -t flask-auth0-api .
docker run -p 3010:3010 flask-auth0-api
Check for multiple required scopes:
def requires_scopes(required_scopes: list) -> bool:
    """Determines if all required scopes are present in the access token"""
    token = get_token_auth_header()
    unverified_claims = jwt.decode(token, options={"verify_signature": False})
    
    if unverified_claims.get("scope"):
        token_scopes = unverified_claims["scope"].split()
        for required_scope in required_scopes:
            if required_scope not in token_scopes:
                return False
        return True
    return False


@APP.route("/api/admin")
@cross_origin(headers=["Content-Type", "Authorization"])
@requires_auth
def admin():
    """Requires both read:messages and write:messages scopes"""
    if requires_scopes(["read:messages", "write:messages"]):
        return jsonify(message="Admin access granted")
    raise AuthError({
        "code": "insufficient_scope",
        "description": "You need both read:messages and write:messages permissions"
    }, 403)
Configure CORS for specific origins:
from flask_cors import CORS

# Allow specific origins
CORS(APP, resources={
    r"/api/*": {
        "origins": ["http://localhost:3000", "https://yourdomain.com"],
        "methods": ["GET", "POST", "PUT", "DELETE"],
        "allow_headers": ["Content-Type", "Authorization"]
    }
})
Implement comprehensive error handling with specific error types:
import logging

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@APP.errorhandler(AuthError)
def handle_auth_error(error: AuthError) -> Response:
    """Handle all Auth0 authentication errors"""
    logger.warning(f"Authentication error: {error.error}")
    response = jsonify(error.error)
    response.status_code = error.status_code
    return response


@APP.errorhandler(404)
def not_found(error):
    """Handle 404 errors"""
    return jsonify({"error": "Not found"}), 404


@APP.errorhandler(500)
def internal_error(error):
    """Handle 500 errors"""
    logger.error(f"Internal server error: {str(error)}")
    return jsonify({"error": "Internal server error"}), 500

Common Issues

Symptom: Getting 401 errors even with valid-looking tokensCause: The audience in your token doesn’t match the audience configured in your APISolution:
  1. Verify the API_IDENTIFIER in your .env file matches your Auth0 API Identifier exactly
  2. The audience is case-sensitive
  3. Ensure the audience is a URL or URN format (e.g., https://my-api not my-api)
Symptom: Token validation fails with issuer mismatchCause: The domain configuration doesn’t match the token issuerSolution:
  1. Verify AUTH0_DOMAIN is correct (e.g., tenant.us.auth0.com)
  2. Don’t include https:// in the domain
  3. Don’t include a trailing slash
Symptom: None values or environment variable errorsCause: Environment variables not loaded or .env file not foundSolution:
  1. Ensure .env file exists in your project root
  2. Verify load_dotenv() is called before accessing environment variables
  3. Check that variable names match exactly: AUTH0_DOMAIN and API_IDENTIFIER
Symptom: jwt.ExpiredSignatureError: Token is expiredCause: The access token has passed its expiration timeSolution:
  1. Request a new token from the Auth0 Dashboard Test tab
  2. Implement token refresh in your client application
  3. Tokens from the Dashboard are typically valid for 24 hours
Symptom: authorization_header_missing errorCause: Request doesn’t include the Authorization header or uses incorrect formatSolution:
  1. Ensure the header is named Authorization (capital A)
  2. Use format: Authorization: Bearer YOUR_TOKEN
  3. Don’t include quotes around the token
Symptom: Invalid header. Use an RS256 signed JWT Access TokenCause: Token is signed with HS256 instead of RS256Solution:
  1. Verify your API is configured to use RS256 signing algorithm in the Auth0 Dashboard
  2. Ensure you’re using an access token, not an ID token
  3. Check that the token was issued for your API audience

Additional Resources

Next Steps

This quickstart is compatible with the Auth0 Python API samples repository. Clone the repository to explore additional examples and deployment options including Docker support.