Authentication in FastAPI | Part 1

3 min read

Authentication mostly means the login part that we do in any website. Authentication in FastAPI doesn't have to be complicated! Let me walk you through a clean, near production-ready JWT authentication system that'll protect our API endpoints.

The Dependencies We Need

PyJWT==2.10.1
python-multipart==0.0.20

Two key packages here:

  • PyJWT: Handles all the JWT encoding/decoding magic
  • python-multipart: Required for OAuth2 form data (username/password)

Let's add a get_current_user dependency in our apis/deps.py file.

import jwt
from sqlmodel import Session, select
from database.db import engine
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer

from core.config import settings
from database.models.user import User

def get_db():
    with Session(engine) as session:
        yield session


oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/token")


def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )

    print("token", token)

    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
        email: str = payload.get("sub")
        if email is None:
            raise credentials_exception
    except jwt.InvalidTokenError:
        raise credentials_exception

    statement = select(User).where(User.email == email)
    result = db.exec(statement)
    user = result.first()
    if user is None:
        raise credentials_exception
    return user

The OAuth2 Setup

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/token")

This single line is doing a lot of heavy lifting! OAuth2PasswordBearer tells FastAPI:

  • "Hey, expect a Bearer token in the Authorization header"
  • "If someone needs to get a token, send them to /api/v1/token"
  • "Generate that nice 'Authorize' button in the docs automatically"

The tokenUrl parameter is crucial - it points to your login endpoint where users exchange username/password for a JWT token.

The Authentication Dependency

def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )

Notice how we're using Depends(oauth2_scheme) to automatically extract the token from the request headers. We can do it manually however, depending on oauth2_scheme helps follow a lot of sanitization and checks.

The credentials_exception is our standard "nope, you're not authorized" response. The WWW-Authenticate: Bearer header tells the client exactly what type of authentication we expect.

JWT Token Validation

try:
    payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
    email: str = payload.get("sub")
    if email is None:
        raise credentials_exception
except jwt.InvalidTokenError:
    raise credentials_exception

Here's where we actually validate the JWT token:

  1. Decode the token using our secret key and algorithm
  2. Extract the email from the "sub" (subject) field
  3. Validate it exists - no email means invalid token
  4. Catch any JWT errors - expired tokens, tampered tokens, etc.

The payload.get("sub") follows JWT standards where "sub" contains the subject (user identifier).

Database User Lookup

statement = select(User).where(User.email == email)
result = db.exec(statement)
user = result.first()
if user is None:
    raise credentials_exception
return user

Once we have a valid email from the token, we need to verify this user actually exists in our database. Using SQLModel's clean syntax:

  • Build a select query for the user
  • Execute it against the database
  • Return the user if found, or throw an exception if not

This double-check ensures that even if someone has a valid JWT token, the user still exists in your system (important if you've deleted users!).

Protecting Your Routes

@router.get("/blogs", response_model=List[ShowBlog])
def get_blogs(db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
    return get_all_blogs(db)

And here's the beautiful part! To protect any endpoint, just add current_user: User = Depends(get_current_user) as a parameter. That's it! 🎉

As soon as you make any of the API endpoints depend on get_current_user which in turn depends on oauth2_scheme. FastAPI will make sure to reward with a authorization button in the OpenAPI docs.

Brainstorming with GPT: 

  • https://claude.ai/share/ecd885e2-1b62-4434-8e46-9f3bc2278f5e
  • https://claude.ai/share/3bd1b91b-98ba-443e-9ca8-31357410d027

I too had a lot of confusion and doubts, I am including my brainstorming questions that I asked GPT. I actually asked a lot more, some are so silly that I feel a bit shy to include them. 

I recently learnt 3 things:

1. When I am writing blogs, I get much more doubts, questions, etc. 

2. Asking questions from GPT helps.

3. Best is experimenting with code and self checking what if this part was like this, what if token url does not exist. The experiments are best way to learn.

FastAPITutorial

My priority is to help build a production-ready application using FastAPI

I prioritize quality over simplicity. Our challenging approach ensures you're prepared for real-world development.

Contacts

Refunds:

Refund Policy
Social

Follow us on our social media channels to stay updated with our latest tutorials and resources.

© Copyright 2022-2025 Team FastAPITutorial. All rights reserved.