> ## Documentation Index
> Fetch the complete documentation index at: https://auth0-docs-event-stream-action-templates.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Flask API

> This guide demonstrates how to integrate Auth0 with any new or existing Python API built with Flask.

export const HowToSchema = () => <script type="application/ld+json">
    {'{"@context":"https://schema.org","@type":"HowTo"}'}
  </script>;

<HowToSchema />

<Note>
  **Prerequisites:** Before you begin, ensure you have the following installed:

  * **Python 3.9** or higher
  * **pip** or Poetry package manager
  * **[jq](https://jqlang.org/)** - Required for Auth0 CLI setup
  * Your preferred code editor

  **Flask Version Compatibility:** This quickstart requires **Flask 3.0** or higher for native async support.
</Note>

## Get Started

This guide demonstrates how to integrate Auth0 with any new or existing Python API built with [Flask](https://flask.palletsprojects.com/).

<Steps>
  <Step title="Create a new Flask project" stepNumber={1}>
    Create a new directory for your Flask API:

    ```bash theme={null}
    mkdir flask-auth0-api
    cd flask-auth0-api
    ```

    Create a virtual environment and activate it:

    ```bash theme={null}
    python -m venv venv
    source venv/bin/activate  # On Windows: venv\Scripts\activate
    ```
  </Step>

  <Step title="Install dependencies" stepNumber={2}>
    Create a `requirements.txt` file with the following dependencies:

    ```txt requirements.txt theme={null}
    flask>=3.0
    auth0-api-python
    python-dotenv
    ```

    Install the dependencies:

    ```bash theme={null}
    pip install -r requirements.txt
    ```
  </Step>

  <Step title="Setup your Auth0 API" stepNumber={3}>
    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:

    <Tabs>
      <Tab title="Dashboard">
        1. Go to the [Auth0 Dashboard](https://manage.auth0.com/) → **Applications** → **APIs**
        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 **Applications** → **Applications** → **\[Your App]** → **Settings**)
        6. Copy the **Identifier** you just created (this is your audience)

        <Info>
          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.
        </Info>
      </Tab>

      <Tab title="CLI">
        Run the following shell command on your project's root directory to create an Auth0 API and generate a `.env` file:

        <CodeGroup>
          ```shellscript Mac theme={null}
          AUTH0_API_NAME="My Flask API" && AUTH0_API_IDENTIFIER="https://my-flask-api" && brew tap auth0/auth0-cli && brew install auth0 && auth0 login --no-input && auth0 apis create --name "${AUTH0_API_NAME}" --identifier "${AUTH0_API_IDENTIFIER}" --signing-alg RS256 --json > auth0-api-details.json && DOMAIN=$(auth0 tenants list --json | jq -r '.[] | select(.active == true) | .name') && AUDIENCE=$(jq -r '.identifier' auth0-api-details.json) && echo "AUTH0_DOMAIN=${DOMAIN}" > .env && echo "AUTH0_AUDIENCE=${AUDIENCE}" >> .env && rm auth0-api-details.json && echo ".env file created with your Auth0 API details:" && cat .env
          ```

          ```shellscript Windows theme={null}
          $ApiName = "My Flask API"; $ApiIdentifier = "https://my-flask-api"; winget install Auth0.CLI; auth0 login --no-input; auth0 apis create -n "$ApiName" -i "$ApiIdentifier" --signing-alg RS256 --json | Set-Content -Path auth0-api-details.json; $Domain = (auth0 tenants list --json | ConvertFrom-Json | Where-Object { $_.active -eq $true }).name; $Audience = (Get-Content -Raw auth0-api-details.json | ConvertFrom-Json).identifier; Set-Content -Path .env -Value "AUTH0_DOMAIN=$Domain"; Add-Content -Path .env -Value "AUTH0_AUDIENCE=$Audience"; Remove-Item auth0-api-details.json; Write-Output ".env file created with your Auth0 API details:"; Get-Content .env
          ```
        </CodeGroup>
      </Tab>
    </Tabs>
  </Step>

  <Step title="Define API permissions" stepNumber={4}>
    Configure permissions (scopes) for your API to control access to specific resources:

    1. In the [Auth0 Dashboard](https://manage.auth0.com/), navigate to **Applications** → **APIs**
    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**

    <Info>
      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.
    </Info>
  </Step>

  <Step title="Configure the Auth0 client" stepNumber={5}>
    <Note>
      If you used the **CLI** method in Step 3, your `.env` file was automatically created. Skip to creating the `app.py` file below.
    </Note>

    If you used the **Dashboard** method, create a `.env` file in your project root to store your Auth0 configuration:

    ```bash .env theme={null}
    AUTH0_DOMAIN=your-tenant.us.auth0.com
    AUTH0_AUDIENCE=https://my-flask-api
    ```

    <Warning>
      Replace `your-tenant.us.auth0.com` with your actual Auth0 domain and update the `API_IDENTIFIER` to match your API identifier from the dashboard.
    </Warning>

    Create an `app.py` file and configure the Auth0 API client:

    ```python app.py theme={null}
    import os
    import asyncio
    from flask import Flask, request, jsonify, g
    from functools import wraps
    from dotenv import load_dotenv
    from auth0_api_python import ApiClient, ApiClientOptions
    from auth0_api_python.errors import BaseAuthError

    # Load environment variables
    load_dotenv()

    app = Flask(__name__)

    # Initialize Auth0 API client (singleton - created once)
    api_client = ApiClient(ApiClientOptions(
        domain=os.getenv("AUTH0_DOMAIN"),
        audience=os.getenv("AUTH0_AUDIENCE")
    ))
    ```
  </Step>

  <Step title="Create protected routes" stepNumber={5}>
    Add a decorator for protecting routes and create public and private endpoints:

    ```python app.py theme={null}
    # Authentication decorator
    def require_auth(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            auth_header = request.headers.get("Authorization", "")
            
            if not auth_header.startswith("Bearer "):
                return jsonify({"error": "Missing or invalid authorization header"}), 401
            
            token = auth_header.split(" ")[1]
            
            try:
                claims = asyncio.run(api_client.verify_access_token(token))
                g.user_claims = claims
                return f(*args, **kwargs)
            except BaseAuthError as e:
                return (
                    jsonify({"error": str(e)}),
                    e.get_status_code(),
                    e.get_headers()
                )
        
        return decorated_function


    # Public endpoint - no authentication required
    @app.route("/api/public", methods=["GET"])
    def public():
        return jsonify({"message": "This endpoint is public"})


    # Protected endpoint - requires authentication
    @app.route("/api/private", methods=["GET"])
    @require_auth
    def private():
        return jsonify({
            "message": "This endpoint requires authentication",
            "user": g.user_claims.get("sub")
        })


    # Protected endpoint with permission validation
    @app.route("/api/private-scoped", methods=["GET"])
    @require_auth
    def private_scoped():
        scopes = g.user_claims.get("scope", "").split()
        
        if "read:messages" not in scopes:
            return jsonify({"error": "Insufficient permissions"}), 403
        
        return jsonify({
            "message": "Private scoped endpoint - read:messages permission granted",
            "user": g.user_claims.get("sub")
        })


    if __name__ == "__main__":
        app.run(debug=True, port=5000)
    ```
  </Step>

  <Step title="Run your API" stepNumber={6}>
    Start your Flask application:

    ```bash theme={null}
    python app.py
    ```

    Your API is now running on `http://localhost:5000`.
  </Step>
</Steps>

<Check>
  **Checkpoint**

  You should now have a fully functional Auth0-protected Flask API running on your localhost with three endpoints:

  * `/api/public` - Accessible without authentication
  * `/api/private` - Requires a valid Auth0 access token
  * `/api/private-scoped` - Requires authentication and the `read:messages` permission
</Check>

## Test Your API

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

### Get a test token

1. Go to the [Auth0 Dashboard](https://manage.auth0.com/)
2. Navigate to **Applications → APIs**
3. Select your API
4. Go to the **Test** tab
5. Copy the access token

### Make a request

Test the public endpoint (no token required):

```bash theme={null}
curl http://localhost:5000/api/public
```

Test the protected endpoint (token required):

```bash theme={null}
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  http://localhost:5000/api/private
```

Replace `YOUR_ACCESS_TOKEN` with the token you copied from the Auth0 Dashboard.

## Advanced Usage

<Accordion title="Validating Custom Claims">
  Require specific claims to be present in the access token:

  ```python theme={null}
  try:
      claims = await api_client.verify_access_token(
          access_token=token,
          required_claims=["email_verified", "org_id"]
      )
  except BaseAuthError as e:
      return jsonify({"error": "Missing required claims"}), 401
  ```
</Accordion>

<Accordion title="DPoP Authentication">
  For enhanced security, enable DPoP (Demonstrating Proof-of-Possession). DPoP enhances OAuth 2.0 by binding access tokens to cryptographic keys.

  ```python theme={null}
  import asyncio


  # Configure API client with DPoP enabled (Allowed mode)
  api_client = ApiClient(ApiClientOptions(
      domain=os.getenv("AUTH0_DOMAIN"),
      audience=os.getenv("AUTH0_AUDIENCE"),
      dpop_enabled=True,   # Default - enables DPoP support
      dpop_required=False  # Default - allows both Bearer and DPoP tokens
  ))

  # For Required mode (DPoP-only, rejects Bearer tokens)
  # dpop_required=True

  # DPoP-aware decorator
  def require_auth_dpop(f):
      @wraps(f)
      def decorated_function(*args, **kwargs):
          headers = {
              "authorization": request.headers.get("Authorization", ""),
              "dpop": request.headers.get("DPoP", "")  # DPoP proof header
          }
          
          try:
              # verify_request() automatically detects Bearer or DPoP scheme
              claims = asyncio.run(api_client.verify_request(
                  headers=headers,
                  http_method=request.method,
                  http_url=request.url
              ))
              g.user_claims = claims
              return f(*args, **kwargs)
          except BaseAuthError as e:
              return jsonify({"error": str(e)}), e.get_status_code(), e.get_headers()
      
      return decorated_function

  @app.route("/api/dpop-protected")
  @require_auth_dpop
  def dpop_protected():
      return jsonify({
          "message": "Successfully authenticated with DPoP or Bearer",
          "user": g.user_claims.get("sub")
      })
  ```

  <Info>
    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.
  </Info>
</Accordion>

<Accordion title="Scope-Based Authorization">
  Create a decorator to check for specific scopes:

  ```python theme={null}
  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"})
  ```
</Accordion>

<Accordion title="Error Handling Best Practices">
  Implement comprehensive error handling with specific error types:

  ```python theme={null}
  from auth0_api_python.errors import (
      BaseAuthError,
      VerifyAccessTokenError,
      GetTokenByExchangeProfileError,
      ApiError
  )

  @app.errorhandler(BaseAuthError)
  def handle_auth_error(error):
      """Handle all Auth0 authentication errors"""
      return jsonify({
          "error": error.get_error_code(),
          "error_description": str(error)
      }), error.get_status_code(), error.get_headers()


  @app.errorhandler(VerifyAccessTokenError)
  def handle_token_verification_error(error):
      """Handle token verification failures specifically"""
      app.logger.warning(f"Token verification failed: {str(error)}")
      return jsonify({
          "error": "invalid_token",
          "error_description": str(error)
      }), 401, error.get_headers()


  @app.errorhandler(ApiError)
  def handle_api_error(error):
      """Handle Auth0 API errors (network, rate limits, etc.)"""
      app.logger.error(f"Auth0 API error: {error.code} - {error.message}")
      return jsonify({
          "error": error.code,
          "error_description": error.message
      }), error.status_code or 500


  @app.errorhandler(Exception)
  def handle_generic_error(error):
      """Catch-all for unexpected errors"""
      app.logger.error(f"Unexpected error: {str(error)}", exc_info=True)
      return jsonify({"error": "Internal server error"}), 500
  ```

  <Info>
    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.
  </Info>
</Accordion>

<Accordion title="Using Before-Request Middleware">
  For applications where most endpoints require authentication, use Flask's `before_request` to validate tokens globally:

  ```python theme={null}
  from flask import g

  PUBLIC_ROUTES = ["/api/public", "/health"]

  @app.before_request
  async 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"]})
  ```
</Accordion>

## Common Issues

<AccordionGroup>
  <Accordion title="401 Unauthorized - Invalid audience">
    **Symptom**: Getting 401 errors even with valid-looking tokens

    **Cause**: The audience in your token doesn't match the audience configured in your API client

    **Solution**:

    1. Verify the `AUTH0_AUDIENCE` 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`)
  </Accordion>

  <Accordion title="401 Unauthorized - Invalid issuer">
    **Symptom**: Token validation fails with issuer mismatch

    **Cause**: The domain configuration doesn't match the token issuer

    **Solution**:

    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
  </Accordion>

  <Accordion title="Configuration values not found">
    **Symptom**: `None` values or environment variable errors

    **Cause**: Environment variables not loaded or `.env` file not found

    **Solution**:

    1. Ensure `.env` file exists in your project root
    2. Verify `load_dotenv()` is called before accessing `os.getenv()`
    3. Check that variable names match exactly (case-sensitive)
  </Accordion>

  <Accordion title="Flask async support errors">
    **Symptom**: `RuntimeError: This event loop is already running` or similar async errors

    **Cause**: Using async routes without Flask 3.0+ or mixing sync/async incorrectly

    **Solution**:

    1. Upgrade to Flask 3.0 or higher: `pip install --upgrade flask`
    2. Ensure all route handlers using `api_client` are declared as `async def`
    3. Don't use `asyncio.run()` within route handlers
  </Accordion>

  <Accordion title="Token expired errors">
    **Symptom**: `VerifyAccessTokenError: Token is expired`

    **Cause**: The access token has passed its expiration time

    **Solution**:

    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
  </Accordion>

  <Accordion title="Missing Authorization header">
    **Symptom**: `Missing or invalid authorization header` error

    **Cause**: Request doesn't include the `Authorization` header or uses incorrect format

    **Solution**:

    1. Ensure the header is named `Authorization` (capital A)
    2. Use format: `Authorization: Bearer YOUR_TOKEN`
    3. Don't include quotes around the token
  </Accordion>
</AccordionGroup>

## Additional Resources

<CardGroup cols={3}>
  <Card title="SDK Documentation" icon="github" href="https://github.com/auth0/auth0-api-python">
    Complete SDK documentation and API reference
  </Card>

  <Card title="Flask Documentation" icon="code" href="https://flask.palletsprojects.com/">
    Official Flask framework documentation
  </Card>

  <Card title="Auth0 Dashboard" icon="gauge" href="https://manage.auth0.com/">
    Manage your Auth0 tenant and APIs
  </Card>

  <Card title="API Authentication Guide" icon="shield" href="https://auth0.com/docs/secure/tokens/access-tokens">
    Learn about access tokens and API security
  </Card>

  <Card title="DPoP Documentation" icon="lock" href="https://auth0.com/docs/secure/sender-constraining/demonstrating-proof-of-possession-dpop">
    Learn about proof-of-possession security
  </Card>

  <Card title="Community Forum" icon="comments" href="https://community.auth0.com/">
    Get help from the Auth0 community
  </Card>
</CardGroup>

## Next Steps

Check out the [Auth0 Python API samples repository](https://github.com/auth0-samples/auth0-python-api-samples) for complete working examples with Flask.
