Authenticate Your FastAPI App with auth0

TL,DR: Want to combine FastAPI and auth0 and have the option for role-based routes? Clone this project to get started.

I manage two halves of one app; one app connects to a database and various services, while the other is a frontend app that takes that info and makes it user-friendly. I'd like to make sure that only my app (and a couple of friends) can access the backend app; here's how I did using FastAPI and auth0.

One solution is to have a 'passport' that the frontend presents to the backend for entry. The backend can check said passport against a central passport system to verify both the passport and access level.

Fortunately a system, JSON Web Tokens, or JWT, exists. FastAPI makes it easy to create your own central passport system; however, this guide will look at using auth0 as the central passport service.

If you'd like a quickstart application, you can clone this repo.

1. Create a Machine App Passport Service

If you don't have an auth0 account, go create a free one. Than, create a machine to machine application. This will allow your backend application to accept and verify tokens from other applications. Keep this tab open. You'll need it later?

auth0-machineapp

2. Install FastAPI and uvicorn (You can skip this step if using the repo)

3. Create a sample route

Create server.py. For this sample project, we'll keep all the routes in the same file, though for larger projects you may want to consider refactoring the routes into a separate folder, similar to Flask Blueprints .

#server.py
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}

Start the app using uvicorn server:app. Using a browser, Postman, or curl, hit localhost:8000. If everything's set up correctly you something like:

fastapi-running

Great! Now that We have a reachable resource, let's deny access.

4. Deny Access to Resource

FastAPI has a Dependency Injection system. Specifying what method should be called in the Depends declaration will call that before the method relating to our endpoint. In our example, we want to check that we find a valid token before we run the endpoint's method.

#server.py
from fastapi import FastAPI,Depends,HTTPException

app = FastAPI()

def aToken():
    if 3>4:
        return True
    else:
        raise HTTPException(
            status_code=401,
            detail="Not authorised to perform this action"
        )

@app.get("/")
def read_root(token:aToken = Depends(aToken)):
    return {"Hello": "World"}

When we hit the app (the route endpoint), we call the method read_root, but now we've told it to call the method aToken before it runs. aToken has been setup to always return false, so an HTTPException will be raised.

After making those changes, your endpoint should now read:

fastapi-exception

Time to get working with some real tokens.

5. Create an auth0 Token Parser

We'll keep all the validation and checking of tokens in one place for ease and reusabilty. In addition, we may want to check for additional roles within in the token (see bonus section below). Rather than parsing the token each time, we can construct a token object.

#auth.py

from fastapi import APIRouter, Header, HTTPException, Depends
from typing import List,Optional
from jose import jwt
from six.moves.urllib.request import urlopen
import json
import ssl
from pydantic import BaseModel

class ValidToken(BaseModel):
    credentials:dict


def validToken(authorization: str = Header(None)):
    if not authorization:
        raise HTTPException(
            status_code=403,
            detail="Access Denied"
        )

In server.py we used the Depends parameter to call the aToken method before the route method. We can now replace this with validToken, whose job is to inspect the header, and raise an exception if no authorization header is found.

If there is indeed a authorization token, check it's legit. To decode the JWT token, you will need to use a library such as python-jose.
Here's a link to the partially completed file.

Remember when you set up that auth0 account and the machine application and I said to keep the tab open because you'll need it later? I hope you did, as we'll need some important information-mainly your domain and API identifier. You can hardcode those in for now, but you should externalise them into environment variables. Really.

To test this, we need to grab a token from our new 'passport office'. If you click on the 'Quickstart' section of your auth0 application, you will see a number of ways to get a shiny token. Copy it, and either using curl or Postman, call localhost:8000, passing in the token under the authorization header.

Here's what you should see when a token is missing:

postman-notoken

But with a token...

postman-token

Now you can decide which endpoints are open to the public, and which need the correct passport, all thanks to auth0!

BONUS

You can add an extra layer of security by including permissions within your auth0 application.

auth0-permissions

Once those permissions have been set, we'll need a way to check for those roles. We'll introduce a new method and variable in our auth.py called hasScope and scopes respectively.

Completed auth example

Now our root method depends on having a valid JWT token and having the correct permissions to get a response.

Hopefully this guide has shown you can still benefit from a new, robust framwork like FastAPI without forgoing the convenience or security of a JWT authorization mechanism like auth0. If you've found this helpful or left something out,let me know.