Implementing Supabase Auth in FastAPI
Today, I’m delving into a recent project where I integrated Supabase’s authentication system into a FastAPI application. This integration not only enhances the security but also bridges the gap between Supabase’s powerful Auth features and FastAPI’s agility. Let’s unpack this, focusing on the user registration, login process, and how tokens are utilized to manage sessions.
User Registration with Supabase Auth
The /register
endpoint in FastAPI plays a pivotal role in creating new users. When a new user registers, the system captures their username, email, and password. Before creating a user, the code checks for invalid characters in the username and ensures password matching.
Linking Supabase Auth with Our User Model
A crucial step in the registration process is creating the user in Supabase using the sign_up
method. This method returns an auth_user
object, which contains a unique UUID (auth_user.user.id
). This UUID is pivotal as it links the Supabase Auth user with our local User model, ensuring synchronization between the two systems.
Here’s a snippet for clarity:
# Creating Supabase Auth User
auth_user = supabase.auth.sign_up(credentials=credentials)
# Linking with our User model
db_user = await User.create(
username=username,
email=email,
super_id=auth_user.user.id, # Linking with Supabase Auth
...
)
The Significance of super_id
The super_id
field in our User model is what ties our system's user with the Supabase Auth user. It ensures that each user in our system has a corresponding, unique identifier in Supabase.
User Login and Token Management
Moving to the login process, the /token
endpoint is where the magic happens. When a user logs in, the system validates their credentials against Supabase's auth system. Upon successful authentication, Supabase returns an auth_user
object, which includes access_token
and refresh_token
.
Setting Cookies for Session Management
The key step here is setting these tokens as cookies in the user’s browser. This is how the application maintains the user’s logged-in state across sessions.
response.set_cookie(
key="access_token",
value=f"Bearer {auth_user.session.access_token}",
httponly=True,
)
Decoding Tokens for User Authentication
For every request, the application needs to verify if the user is logged in. This is where the _supabase_user
dependency comes into play. It decodes the JWT token (from the access_token
cookie) to authenticate the user. If the token is valid, the application fetches the user details from our database without hitting Supabase again.
decoded = jwt.decode(
access_token,
key=settings.JWT_SECRET, <---- You get this from supabase cms
do_verify=True,
algorithms=["HS256"],
audience="authenticated",
)
user_metadata = decoded.get("user_metadata", {})
super_id = decoded.get("sub")
email = decoded.get("email")
username = user_metadata.get("username")
return UserFrontEnd(
super_id=super_id,
username=username if username else email,
email=email,
is_anonymous=False,
)
Using Dependencies for Session Tracking and User Authentication
The application uses two dependencies: _supabase_user
and _supabase_user_logged_in
. The former tracks if a user is logged in (even for anonymous users), while the latter enforces user authentication, redirecting to the login page if the user isn’t logged in.
Middleware and Exception Handling for Seamless User Experience
Finally, our FastAPI app uses middleware to attach the user object (request.state.user
) to every request. This allows for conditional rendering in templates based on the user's logged-in state. Additionally, custom exception handlers manage redirections, like pushing unauthenticated users to the login page.
Conclusion
By integrating Supabase’s auth system with FastAPI, we’ve established a secure, robust, and efficient user authentication process. This setup not only ensures security but also provides a seamless user experience. Future enhancements, like password resets, can be easily added to this foundation, making it a versatile solution for modern web applications.