Skip to content

Guarding the Gates: Security and Authentication

Guarding the Gates: Security and Authentication

Section titled “Guarding the Gates: Security and Authentication”

Security can be daunting, but FastAPI provides tools to implement standard security protocols like OAuth2 without requiring you to be a cryptography expert. We will focus on the OAuth2 with Password (and Hashing) flow, which is ideal for first-party applications (like a frontend and backend owned by the same organization).

In this flow:

  • The user sends a username and password to the API.
  • The API verifies the credentials.
  • The API responds with a token (usually a JWT - JSON Web Token).
  • The frontend stores this token and sends it in the Authorization header for subsequent requests.

First, we define how the API expects to receive the token using OAuth2PasswordBearer. The tokenUrl parameter tells the frontend (and the automatic docs) where to send the username/password to get the token.

from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from typing import Annotated
app = FastAPI()
# The URL where the user sends their credentials
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

The core of our security is a dependency commonly named get_current_user. This dependency will rely on the oauth2_scheme we created above.

When you use Depends(oauth2_scheme), FastAPI will automatically look for the Authorization header, extract the Bearer token, and pass it as a string to your function. Inside get_current_user, you would typically:

  • Decode the JWT token.
  • Check if it is expired.
  • Extract the user ID.
  • Fetch the user from the database.
  • If any step fails, raise a HTTPException with status code 401 (Unauthorized).
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
user = decode_token_and_find_user(token) # Pseudocode logic
if not user:
raise HTTPException(
status_code=401,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return user

Now, protecting a route is as simple as adding the dependency to the path operation function. By adding Depends(get_current_user), you ensure that the code inside the function only runs if a valid user was found.

@app.get("/users/me")
async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]):
return current_user

A massive benefit here is the automatic interactive documentation. Swagger UI will now show an “Authorize” button, allowing you to log in directly within the documentation to test your secured endpoints.