Session-Based Authentication with FastAPI: A Step-by-Step Guide
If you need a quick refresher, feel free to revisit previous blog posts
Building CRUD API with FastAPI: A Step-by-Step Guide
Introduction
Web applications often handle sensitive user data and require robust authentication mechanisms to protect against unauthorized access. Session-based authentication is a widely used approach to manage user sessions securely. In this blog post, we will explore how to implement session-based authentication in FastAPI, a powerful and efficient Python web framework.
Session-based authentication is a common security mechanism used in web applications to manage user sessions and ensure secure communication between the client and the server. It involves in creating a unique session for each authenticated user upon successful login. Session-based authentication is a common security mechanism used in web applications to manage user sessions and ensure secure communication between the client and the server.
Welcome to the “Authenticating with FastAPI: The Epic Saga of Session-Based Awesomeness!” 🚀
Caution: You may see password in the responses. In a real-world application, it is crucial never to return passwords in the API response.
Preparation
- Setup and Dependencies: The code snippet begins by importing necessary modules from FastAPI, including
FastAPI
,Depends
,HTTPException
, and others. It also importsHTTPBasic
fromfastapi.security
for HTTP basic authentication.
from fastapi import FastAPI, Depends, HTTPException, status, APIRouter
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import random
from fastapi import Request
from fastapi import Body
- Creating a User Database: For demonstration purposes, the code includes a dictionary named
users
serves as an in-memory user database for demonstration purposes. It contains sample user details, once they signup. - Creating Sessions: The
sessions
dictionary will store session IDs and their corresponding user IDs when the user logs in.
# User Database (for demonstration purposes)
users = {}
# In-memory session storage (for demonstration purposes)
sessions = {}
- Have You signed up?
- The user needs to sign up first .This endpoint basically handles user registration and ensures that the username is unique before adding the new user to the
users
dictionary. In real projects it should be added to a users table in a database.
@app.post("/signup")
def sign_up(username: str = Body(...), password: str = Body(...)):
user = users.get(username)
if user:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Username already exists",
)
new_user_id = len(users) + 1
new_user = {
"username": username,
"password": password,
"user_id": new_user_id
}
users[username] = new_user
return {"message": "User registered successfully"}
How will your post request look in postman
Let’s assume, two more users also signed up by using the signup page.
{
"user1": {
"username": "user1",
"password": "xxxxxx",
"user_id": 1
},
"user2": {
"username": "user2",
"password": "yyyyyy",
"user_id": 2
},
"user3": {
"username": "user3",
"password": "zzzzzz",
"user_id": 3
}
}
- Once you have signed up , you are ready to login to the application.
- You need to submit your user and password to login. It basically uses the login endpoint.The helper function named authenticate_user that is responsible for checking user credentials during the login.
# Login endpoint - Creates a new session
@router.post("/login")
def login(user: dict = Depends(authenticate_user)):
session_id = create_session(user["user_id"])
return {"message": "Logged in successfully", "session_id": session_id}
- It utilizes HTTP basic authentication with the
HTTPBasicCredentials
, which represents the username and password provided by the client in the Authorization header of the HTTP request.If the provided credentials are invalid, it raises anHTTPException
with a 401 Unauthorized status. The Depends function tells FastAPI to execute the security object (which represents HTTP Basic Authentication) and obtain the user credentials from the request.
def authenticate_user(credentials: HTTPBasicCredentials = Depends(security)):
user = users.get(credentials.username)
if user is None or user["password"] != credentials.password:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials",
headers={"WWW-Authenticate": "Basic"},
)
return user
- Session Management Helper Functions: Once it obtains the user ID , then it generates a new session ID and associates it with the user ID, then stores the session in the
sessions
dictionary.
def create_session(user_id: int):
session_id = len(sessions) + random.randint(0, 1000000)
sessions[session_id] = user_id
return session_id
- Check your user details.
- It will retrieve the current user’s information based on the session ID. You can pass the session id in cookies as part of header.
# Get current user endpoint - Returns the user corresponding to the session ID
@router.get("/getusers/me")
def read_current_user(user: dict = Depends(get_user_from_session_id)):
return user
- You are autheticated now !!! Are you ready to access other pages ?
- You’re now entering the VIP zone of the web application, but no ticket? No access! Pass the test of authentication and join the party! 🎉🎟️💻
# Protected endpoint - Requires authentication
@router.get("/protected")
def protected_endpoint(user: dict = Depends(get_authenticated_user_from_session_id)):
if user is None:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authenticated")
return {"message": "This user can connect to a protected endpoint after successfully autheticated", "user": user}
- get_authenticated_user_from_session_id and get_user_from_session acts as a custom middleware for session-based authentication. It retrieves the session ID from the request cookies, validates it, and returns the corresponding user's information.
# Custom middleware for session-based authentication
def get_authenticated_user_from_session_id(request: Request):
session_id = request.cookies.get("session_id")
if session_id is None or int(session_id) not in sessions:
raise HTTPException(
status_code=401,
detail="Invalid session ID",
)
# Get the user from the session
user = get_user_from_session(int(session_id))
return user
# Use the valid session id to get the corresponding user from the users dictionary
def get_user_from_session(session_id: int):
user = None
for user_data in users.values():
if user_data['user_id'] == sessions.get(session_id):
user = user_data
break
return user
Your get request would look like this in postman and you will need to pass the cookie in the header.
- Finally it’s time to log out
- Time to bid farewell. It’s time to log out and say goodbye to your trusty session. Adieu, session! You served us well, but now it’s time to vanish into the coding abyss.
# Logout endpoint - Removes the session
@router.post("/logout")
def logout(session_id: int = Depends(get_session_id)):
if session_id not in sessions:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Session not found")
sessions.pop(session_id)
return {"message": "Logged out successfully", "session_id": session_id}
- It would need this helper function to fetch the session id for the current session.
# Create a new dependency to get the session ID from cookies
def get_session_id(request: Request):
session_id = request.cookies.get("session_id")
if session_id is None or int(session_id) not in sessions:
raise HTTPException(status_code=401, detail="Invalid session ID")
return int(session_id)
Conclusion:
In this blog post, we explored session-based authentication in FastAPI. We dissected a code example demonstrating user registration, login, logout, and secure access to protected resources using session-based authentication. With session-based authentication, your application can securely manage user sessions and protect sensitive data from unauthorized access.
By following the step-by-step guide provided here, you can confidently implement session-based authentication in your FastAPI applications, enhancing their security and reliability.