Navigate back to the homepage

Demystifying authentication with FastAPI and a frontend

Alexis Tacnet
December 2nd, 2020 · 7 min read

Authentication is definitely a hard and complicated problem. Protocols like OAuth2 try to make it simpler, but in fact they make it harder to understand for beginners, as the reference is quite complex and there aren’t a lot of good practices available online.

The new framework FastAPI is now our go-to web library for all our projects, as it is very efficient to develop with and it supports amazing typing out of the box. The only issue we have is dealing with authentication when using a JS Frontend in front of it. Let’s close this debate once and for all by describing the authentication scheme that I think everyone needs for a simple web application with FastAPI, using an external provider.

The code of this article is now available on Github here ! I created a example application to illustraste this article.

The authentication plan

First, we need to have a plan for what we want to do. I have always wondered why authentication tutorials cover hundreds of different cases. In fact, I am convinced that a modern web application shall just need one.

What I mean by modern web application here is a FastAPI Python backend (but you can go with whatever you like as long as it outputs JSON) with a JS frontend (with React or Vue for example). What we need to do is therefore authenticate someone navigating on the frontend, and establish a secure connection with the backend to know who is calling your API.

Simple right? And because we are in the 21st century, let’s not use a lame email/password form on the frontend : they are not really secure, and SSO (Single Single-On) is clearly a marketing advantage nowadays. We will therefore use a OAuth-based authentication scheme to get information and authenticate the user on our website.

The goal of OAuth

You may have heard of it, because every major auth provider supports: Google, Twitter, Github, Facebook, … but you may wonder why it exists in the first place!

The goal of OAuth in general is to streamline and standardize the use of authorization around the web, especially when it comes to multiple parties like our website and Google for example. How do you access the Google information from our website with a Google login? This is what OAuth is made for.

In this tech story, we have as always both sides : the server and the client. In the case of a external provider like Github, Github is the OAuth server, and our application is the client. The goal of OAuth is to enable the client to query the API server attached to the OAuth server in a secure way.

What we want to do is ask Google who the user on the website is to achieve authentication. We will store this user information in a database, and then secure the connection for this user between the frontend and the backend to identify and authorize API calls.

But keep in mind: OAuth2 is made to make it easy to authorize our server to interact with other parties like Google or Github on behalf of an user. You can then ask them who the user on the website is to make authentication, for authenticating the link between the frontend and our API.

Securing the connection between the frontend and our API

What we need in the end is to authenticate our connection between our frontend (usually a SPA or SSR javascript application) and our own API. For that, we will use the famous JWT token that has several advantages and that I really like.

The protocol is the following:

  • Google redirects to our application with a code
  • We ask Google for more user information, like the profile picture
  • We then issue a JWT token with this user data that we will share between the frontend and the backend

This token will be used for every API call to our own backend and will therefore identify the user making the calls.

We don’t use OAuth directly to secure the connection between the frontend and our API, because we don’t need it, and that is not what it’s made for. We only issue a small token, and we verify it for each request in the backend. Secure. Simple :)

Authentication pattern

If you already looked at OAuth2, you may have noticed that several schemes are available and are needed for different types of applications. The two that we are interested in are the Implicit one and the Authorization code scheme.

The Implicit scheme

With the Implicit scheme, our user is redirected to the SSO login (1) and then redirected after authentication to our frontend (2) with the access_token needed to access the OAuth resources (profile picture remember). This is typically useful when we need to access this type of resource directly in the frontend, without bothering making the backend in charge of that.

Implicit scheme schema

To secure the connection between the frontend and our own API, you need to give this token to the API (3) that will check with the OAuth server that it is valid (4). If the token is valid, then this user is clearly on the website, so let’s give him another token (5) (to check it super quickly without asking the OAuth server every time and control its expiration for example).

As mentioned, our frontend is also capable of querying directly the external API (6), with the access_token, to process on its side the resources the user gave access to.

The Authorization code scheme

With the Authorization code scheme, our user is also redirected to the SSO login (1) and then redirected to our frontend (2), but with a mysterious code that we need to give to the backend (3).

Authorization code scheme schema

The backend will then give this code (along with the OAuth secret client data, like client_id and client_secret) to the OAuth server that will then return an access_token if the code is correct (4).

If the code is correct, we can use the token given by the OAuth server to access user resources directly from the backend (5), to store a profile picture or a Google doc file that belongs to the user for example. Then, our user is authenticated, so let’s finally give him his token (6).

The advantage of this scheme is we don’t put the frontend in charge of the access_token of the OAuth server, which has a few security advantages:

  • The frontend doesn’t have to give this token to the backend, which is better for security as a Man-in-the-middle attack could steal your identity. This is not possible with a code as it needs a client_secret which only the backend is aware of.
  • The backend is the one with the access_token, preventing any malicious extensions from stealing it in your front-end domain.

In our experience, we prefer this scheme for production applications. This is the only one supported by Github for example.

FastAPI implementation

I really like FastAPI: this framework is simple, efficient, and typing friendly. Creating a authentication scheme on top of it was not that hard, and is really clean. We just have to keep in mind the few tips I described earlier:

  • OAuth is only for external API access
  • Simple JWT encoding and decoding between our frontend and backend

In this example, we will use the Authorization Code scheme, with this code forwarded between our frontend and our backend, and the Github external provider.

URLs

The first thing to configure are the different URLs we will use for this scheme:

  • The LOGIN_URL is the base URL needed to create the URL our frontend will be redirected to
  • The TOKEN_URL is the one our backend will query with the Authorization code to get an access_token.
  • The USER_URL is an api endpoint used to get data about the user, it is a typical example of the external API described earlier.
  • The REDIRECT_URL is the URL of our frontend that Github will need to redirect to, it is useful because it will be passed with the LOGIN_URL.
1from app.settings import settings
2
3LOGIN_URL = "https://github.com/login/oauth/authorize"
4TOKEN_URL = "https://github.com/login/oauth/access_token"
5USER_URL = "https://api.github.com/user"
6
7REDIRECT_URL = f"{settings.app_url}/auth/github"

Login URL creation

The frontend needs to redirect the user’s browser to a URL generated from the LOGIN_URL but also with some information specific to our application:

  • The client_id, given by Github for our application
  • The redirect_uri, that we want Github to forward the user to, with the Authorization code
  • The state, a random generated string that Github will check again when we want to create the access_token, for security reasons

The code is quite straightforward, we created a route called /login so that we can tell the frontend which URL to use. We use an APIRouter here because we want to integrate this piece of code directly into an existing FastAPI application.

1from urllib.parse import urlencode
2
3from fastapi import APIRouter
4
5from app.settings import settings
6from .schemas import Url
7from .helpers import generate_token
8
9LOGIN_URL = "https://github.com/login/oauth/authorize"
10REDIRECT_URL = f"{settings.app_url}/auth/github"
11
12router = APIRouter()
13
14@router.get("/login")
15def get_login_url() -> Url:
16 params = {
17 "client_id": settings.github_client_id,
18 "redirect_uri": REDIRECT_URL,
19 "state": generate_token(),
20 }
21 return Url(url=f"{LOGIN_URL}?{urlencode(params)}")

The schema Url is defined like this:

1from pydantic import BaseModel
2
3class Url(BaseModel):
4 url: str

Authorization code verification and token creation

Once the frontend receives the Authorization code, it can forward it to a specific API endpoint, here /authorize for the backend to check it and get all it needs from the User API. The first thing we want to define are the schemas that will be used:

1from pydantic import BaseModel
2
3class AuthorizationResponse(BaseModel):
4 state: str
5 code: str
6
7class GithubUser(BaseModel):
8 login: str
9 name: str
10 company: str
11 location: str
12 email: str
13 avatar_url: str
14
15class User(BaseModel):
16 id: int
17 login: str
18 name: str
19 company: str
20 location: str
21 email: str
22 picture: str
23
24 class Config:
25 orm_mode = True
26
27class Token(BaseModel):
28 access_token: str
29 token_type: str
30 user: User

The AuthorizationResponse is the body of the request made by the frontend with the state and authorization code, while the GithubUser and User represent users from different sources. The Token schema defines what we will send to the frontend to authenticate our requests between our API and the interface.

On the route side, we use httpx to make requests to Github and check the authorization code, as well as retrieving the user’s information. We then create a database entry if the user is not yet present in the database, using the sqlachemy ORM. This is a plain implementation of the Github documentation on their auth API.

1from urllib.parse import parse_qsl
2from typing import Dict
3
4import httpx
5from fastapi import APIRouter, Depends
6from sqlalchemy.orm import Session
7
8from app.database import get_db
9from .helpers import create_access_token
10from .schemas import AuthorizationResponse, GithubUser, User, Token
11from .crud import get_user_by_login, create_user
12
13TOKEN_URL = "https://github.com/login/oauth/access_token"
14USER_URL = "https://api.github.com/user"
15
16router = APIRouter()
17
18@router.post("/authorize")
19async def verify_authorization(
20 body: AuthorizationResponse, db: Session = Depends(get_db)
21) -> Token:
22 params = {
23 "client_id": settings.github_client_id,
24 "client_secret": settings.github_client_secret,
25 "code": body.code,
26 "state": body.state,
27 }
28
29 async with httpx.AsyncClient() as client:
30 token_request = await client.post(TOKEN_URL, params=params)
31 response: Dict[bytes, bytes] = dict(parse_qsl(token_request.content))
32 github_token = response[b"access_token"].decode("utf-8")
33 github_header = {"Authorization": f"token {github_token}"}
34 user_request = await client.get(USER_URL, headers=github_header)
35 github_user = GithubUser(**user_request.json())
36
37 db_user = get_user_by_login(db, github_user.login)
38 if db_user is None:
39 db_user = create_user(db, github_user)
40
41 verified_user = User.from_orm(db_user)
42 access_token = create_access_token(data=verified_user)
43
44 return Token(access_token=access_token, token_type="bearer", user=db_user)

The function create_access_token creates a simple JWT token, and is therefore in the helpers file:

1from datetime import datetime, timedelta
2
3import jwt
4
5from app.settings import settings
6from .schemas import User
7
8def create_access_token(*, data: User, exp: int = None) -> bytes:
9 to_encode = data.dict()
10 if exp is not None:
11 to_encode.update({"exp": exp})
12 else:
13 expire = datetime.utcnow() + timedelta(minutes=60)
14 to_encode.update({"exp": expire})
15 encoded_jwt = jwt.encode(
16 to_encode, settings.jwt_secret_key, algorithm=settings.jwt_algorithm
17 )
18 return encoded_jwt

Get user from the JWT token

Now that our frontend has a JWT token, we just need to secure our private routes with a FastAPI Dependency that will decode the token and raise an Exception if needed. The dependency is made like this, in its own file:

1import jwt
2from fastapi import Header, HTTPException, status
3from fastapi.security.utils import get_authorization_scheme_param
4from pydantic import ValidationError
5
6from app.settings import settings
7from .schemas import User
8
9def get_user_from_header(*, authorization: str = Header(None)) -> User:
10 credentials_exception = HTTPException(
11 status_code=status.HTTP_401_UNAUTHORIZED,
12 detail="Could not validate credentials",
13 headers={"WWW-Authenticate": "Bearer"},
14 )
15
16 scheme, token = get_authorization_scheme_param(authorization)
17 if scheme.lower() != "bearer":
18 raise credentials_exception
19
20 try:
21 payload = jwt.decode(
22 token, settings.jwt_secret_key,
23 algorithms=[settings.jwt_algorithm]
24 )
25 try:
26 token_data = User(**payload)
27 return token_data
28 except ValidationError:
29 raise credentials_exception
30 except jwt.PyJWTError:
31 raise credentials_exception

We can use this dependency in some private endpoints, like the common /me that will give information of the user making the request (through its JWT token):

1from fastapi import APIRouter, Depends, HTTPException
2from sqlalchemy.orm import Session
3
4from app.database import get_db
5
6from .crud import get_user
7from .schemas import User
8from .models import User as DbUser
9from .dependency import get_user_from_header
10
11router = APIRouter()
12
13@router.get("/me", response_model=User)
14def read_profile(
15 user: User = Depends(get_user_from_header),
16 db: Session = Depends(get_db),
17) -> DbUser:
18 db_user = get_user(db, user.id)
19 if db_user is None:
20 raise HTTPException(status_code=404, detail="User not found")
21 return db_user

Conclusion

Authentification is a very difficult beast to tame, and improvements can be made on our own approach. However, this FastAPI example should give you a good start to implement your own scheme, using whatever external provider you like (Google, Facebook, Twitter, …). And suprise, you can check the Github repository for this article right there : https://github.com/fuegoio/fastapi-frontend-auth-example, I created a example app to illustrate every principle I wrote about.

More articles from Kernel Panic

The modern way to call APIs in Python

Asynchronous and typing are new standards in Python, and they are especially useful for developing API clients to abstract and test external API response.

November 25th, 2020 · 4 min read
© 2020 Kernel Panic
Link to $https://twitter.com/kernelpanicio