Python API Authorization

Sample Project

Download a sample project specific to this tutorial configured with your Auth0 API Keys.

System Requirements
  • python 2.7, 3.3 and up
  • flask 0.11.1
  • python-jose-cryptodome 1.3.2
  • flask-cors 3.0.2
  • six 1.10.0
Show requirements

This tutorial shows you how to use the authorization features in the OAuth 2.0 framework to limit access to your or third-party applications. For more information, read the API authorization documentation.

Create a Resource Server (API)

In the APIs section of the Auth0 dashboard, click Create API. Provide a name and an identifier for your API. You will use the identifier as an audience later, when you are configuring the access token verification. For Signing Algorithm, select RS256.

Create API

Add API Authorization

To restrict access to the resources served by your API, check the incoming requests for valid authorization information. The authorization information is stored in the access token created for the user and needs to be sent in the Authorization header. To see if the token is valid, check it against the JSON Web Key Set (JWKS) for your Auth0 account. To learn more about validating access tokens, read the Verify Access Tokens tutorial.

Install the Dependencies

This quickstart demonstrates how to add authorization to your Python API using Flask. Add the following dependencies to your requirements.txt:

# /requirements.txt

flask
python-dotenv
python-jose-cryptodome
flask-cors
six

Create the Flask APP

Create a server.py file and initializate the Flask App. Set the domain, audience and the error handling.

# /server.py

import json
from six.moves.urllib.request import urlopen
from functools import wraps

from flask import Flask, request, jsonify, _request_ctx_stack
from flask_cors import cross_origin
from jose import jwt

AUTH0_DOMAIN = 'YOUR_AUTH0_DOMAIN'
API_AUDIENCE = YOUR_API_AUDIENCE
ALGORITHMS = ["RS256"]

APP = Flask(__name__)

# Error handler
class AuthError(Exception):
    def __init__(self, error, status_code):
        self.error = error
        self.status_code = status_code
    
@APP.errorhandler(AuthError)
def handle_auth_error(ex):
    response = jsonify(ex.error)
    response.status_code = ex.status_code
    return response

Create the JWT Validation Decorator

By default, your API will be set up to use RS256 as the algorithm for signing tokens. Since RS256 works by using a private/public keypair, tokens can be verified against the public key for your Auth0 account. This public key is accessible at https://YOUR_AUTH0_DOMAIN/.well-known/jwks.json.

Add a decorator which verifies the access_token against your JWKS.

# /server.py

# Format error response and append status code
def get_token_auth_header():
    """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)
    elif len(parts) == 1:
        raise AuthError({"code": "invalid_header",
                        "description": "Token not found"}, 401)
    elif len(parts) > 2:
        raise AuthError({"code": "invalid_header",
                        "description":
                            "Authorization header must be"
                            " Bearer token"}, 401)

    token = parts[1]
    return token

def requires_auth(f):
    """Determines if the access token is valid
    """
    @wraps(f)
    def decorated(*args, **kwargs):
        token = get_token_auth_header()
        jsonurl = urlopen("https://"+AUTH0_DOMAIN+"/.well-known/jwks.json")
        jwks = json.loads(jsonurl.read())
        unverified_header = jwt.get_unverified_header(token)
        rsa_key = {}
        for key in jwks["keys"]:
            if key["kid"] == unverified_header["kid"]:
                rsa_key = {
                    "kty": key["kty"],
                    "kid": key["kid"],
                    "use": key["use"],
                    "n": key["n"],
                    "e": key["e"]
                }
        if rsa_key:
            try:
                payload = jwt.decode(
                    token,
                    rsa_key,
                    algorithms=ALGORITHMS,
                    audience=API_AUDIENCE,
                    issuer="https://"+AUTH0_DOMAIN+"/"
                )
            except jwt.ExpiredSignatureError:
                raise AuthError({"code": "token_expired",
                                "description": "token is expired"}, 401)
            except jwt.JWTClaimsError:
                raise AuthError({"code": "invalid_claims",
                                "description":
                                    "incorrect claims,"
                                    "please check the audience and issuer"}, 401)
            except Exception:
                raise AuthError({"code": "invalid_header",
                                "description":
                                    "Unable to parse authentication"
                                    " token."}, 400)

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

Use this Decorator in your Methods

You can now use the decorator in any routes that require authentication.

# Controllers API

# This doesn't need authentication
@app.route("/api/public")
@cross_origin(headers=['Content-Type', 'Authorization'])
def public():
    response = "All good. You don't need to be authenticated to call this"
    return jsonify(message=response)

# This does need authentication
@app.route("/api/private")
@cross_origin(headers=['Content-Type', 'Authorization'])
@requires_auth
def private():
    response = "All good. You only get this message if you're authenticated"
    return jsonify(message=response)

Protect individual endpoints

Individual routes can be configured to look for a particular scope in the access_token by using the following:

# /server.py

def requires_scope(required_scope):
    """Determines if the required scope is present in the access token
    Args:
        required_scope (str): The scope required to access the resource
    """
    token = get_token_auth_header()
    unverified_claims = jwt.get_unverified_claims(token)
    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

Then, establish what scopes are needed in order to access the route. In this case read:messages is used:

# /server.py

@APP.route("/api/private-scoped")
@cross_origin(headers=["Content-Type", "Authorization"])
@cross_origin(headers=["Access-Control-Allow-Origin", "*"])
@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 = "All good. You're authenticated and the access token has the appropriate scope."
        return jsonify(message=response)
    raise AuthError({
        "code": "Unauthorized",
        "description": "You don't have access to this resource."
    }, 403)
Next Tutorial
2. Using your API
Was this article helpful?
Use Auth0 for FREECreate free Account