Integrating FastAPI with Supabase Auth

j0

Lee Yi Jie Joel

Posted on March 5, 2023

Integrating FastAPI with Supabase Auth

Why this article and what we'll cover

In this article we'll go over the basics of how to implement login and logout functionality using Supabase Auth. We'll also go over how to guard routes so that only signed in users can access protected routes.

What is Supabase Auth

Supabase is a JSON Web Token based Auth service - it takes in the credentials of a user (for instance email and password) and returns a token that is used to securely transit information between parties. Other services can then make use of this token to know more about the user. For example, we can determine the user's role as well as the authentication methods that they have used to sign in.

Creating a sign up route

If integrating with Supabase Auth, one would simply need to wrap the client library in a route in order to get sign in, sign up, and sign out functionality. Here's an example:

Initialise a client in a centralised file

import os
from supabase import create_client, Client

url: str = os.environ.get("SUPABASE_URL")
key: str = os.environ.get("SUPABASE_KEY")
supa: Client = create_client(url, key)
Enter fullscreen mode Exit fullscreen mode

Import the client into your application logic:

from .supabase import supa

@app.get("/sign_up")
def sign_up():
  res = supa.auth.sign_up(email="testsupa@gmail.com",
                          password="testsupabasenow")
  return res.get("access_token")

@app.get("/sign_out")
def sign_out():
  supa = init_supabase()
  res = supa.auth.sign_out()
  return "success"

@app.get("/sign_in")
def sign_in():
  supa = init_supabase()
  res = supa.auth.sign_in_with_password({"email":"testsupa@gmail.com", "password": "testsupabasenow"})
  return res.get("access_token")
Enter fullscreen mode Exit fullscreen mode

To protect a route, there are two main options:

  1. We can write a middleware function to validate the incoming JWT
  2. We can use an extended version of the default FastAPI HTTPBearer together with Dependency Injection to validate the route

We're going to go with the second option as it is slightly less verbose.

Let's extend the built in HTTP Bearer class to decode and validate the JWT. We can do so by making use of a jwt library like python-jose or PyJWT. We'll also need the Supabase JWT secret which is under Settings > Auth at time of writing.

JWT Bearer class

Let's extend the built in JWT Bearer - below is a snippet taken from TestDriven.io's blog which performs a verification of the JWT to ensure that credentials are valid.

class JWTBearer(HTTPBearer):
    def __init__(self, auto_error: bool = True):
        super(JWTBearer, self).__init__(auto_error=auto_error)

    async def __call__(self, request: Request):
        credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request)
        if credentials:
            if not credentials.scheme == "Bearer":
                raise HTTPException(status_code=403, detail="Invalid authentication scheme.")
            if not self.verify_jwt(credentials.credentials):
                raise HTTPException(status_code=403, detail="Invalid token or expired token.")
            return credentials.credentials
        else:
            raise HTTPException(status_code=403, detail="Invalid authorization code.")

    def verify_jwt(self, jwtoken: str) -> bool:
        isTokenValid: bool = False

        try:
            payload = decodeJWT(jwtoken)
        except:
            payload = None
        if payload:
            isTokenValid = True
        return isTokenValid
Enter fullscreen mode Exit fullscreen mode

In order to protect the route, we can import the extended HTTP Bearer class and use the dependencies, option in FastAPI to protect the route.

Here's what it would look like:

from auth import JWTBearer
@app.post('/my-protected-route/',dependencies=[Depends(JWTBearer())], response_model=SchemaJob)
async def job(job: SchemaJob):
    ...
    # Some work here
Enter fullscreen mode Exit fullscreen mode

Voila, you have a protected route.With this protection, requests to protected routes will raise an error if the Authorization Bearer: <jwt-token> header is not included.

It is also possible to extend this implementation by implementing custom checks based on claims or roles in def __call__ of the JWTBearer class above. For more information on JWTs, you may wish to check out the Supabase website Auth section for a deeper look into how JWTs function.

Here's the sample repo. Please feel free to leave any questions in the comments below.

The example code draws reference from:

  1. https://testdriven.io/blog/fastapi-jwt-auth/
  2. https://educative.io/answers/how-to-use-postgresql-database-in-fastapi
💖 💪 🙅 🚩
j0
Lee Yi Jie Joel

Posted on March 5, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related