DEV Community

zwx00
zwx00

Posted on • Updated on

Validating a Supabase JWT locally with Python and FastAPI

Supabase is nice, but the functionality it provides for verifying tokens (auth.get_user) for some inexplicable reasons makes a round trip to their end, and this trip in fact takes quite a while (up to 600ms).

JWT tokens should by their very nature be verifiable locally, provided we know the secret. Luckily Supabase does expose this JWT secret under project settings.

Here's how a FastAPI dependency to get user email and verify their authenticity using the token alone might look like:

import jwt
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer

from katalystai.conf import SETTINGS



async def get_user_email(
    res: Response,
    cred: HTTPAuthorizationCredentials = Depends(HTTPBearer(auto_error=False)),
):

    if cred is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Bearer authentication required",
            headers={"WWW-Authenticate": 'Bearer realm="auth_required"'},
        )

    try:
        jwt_result = jwt.decode(
            cred.credentials,
            SETTINGS.supabase_jwt_secret,
            audience="authenticated",
            algorithms=["HS256"],
        )

        return jwt_result["email"]
    except jwt.exceptions.PyJWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication credentials",
            headers={"WWW-Authenticate": 'Bearer realm="auth_required"'},
        )
Enter fullscreen mode Exit fullscreen mode

We can then use this "middleware" like so:

from fastapi import Depends
from katalystai.auth import get_user_email

@router.post
async def create_thing(user_email: str = Depends(get_user_email)):
    pass
Enter fullscreen mode Exit fullscreen mode

Supabase python client overall is not that nice, it's kinda slow and unreliable. I also suggest switching to another ORM and communicating with Postgres directly instead of using their community python client. I had luck using tortoise, which has excellent async support and clean Django-like API.

Top comments (0)