Python Fastapi Jwt Oauth2 Authentication

Written by

in

In the fast‑moving world of web development, Python FastAPI has quickly become the go‑to framework for building high‑performance APIs. One of the most common challenges when exposing endpoints to the public is securing them with robust authentication. In this guide we’ll walk through everything you need to know about implementing JWT OAuth2 authentication in FastAPI—from the core concepts to a complete, production‑ready code example. By the end, you’ll have a solid foundation to protect your APIs, manage user sessions, and scale your authentication strategy with confidence.

Why Choose FastAPI for Authentication?

FastAPI stands out for several reasons that make authentication both simple and powerful:

  • Async‑first design – Handles thousands of concurrent connections without blocking.
  • Native pydantic models – Guarantees data validation and clear request/response schemas.
  • Built‑in support for OAuth2 and Security utilities – Reduces boilerplate code.
  • Automatic OpenAPI documentation – Your authentication flow is instantly visible in Swagger UI.

Core Concepts: JWT and OAuth2

What is JWT?

A JSON Web Token (JWT) is a compact, URL‑safe means of representing claims to be transferred between two parties. A JWT consists of three parts—header, payload, and signature—encoded in Base64URL and separated by dots:

eyJhbGciOi... .eyJzdWIiOi... .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Key benefits of JWTs for API authentication include statelessness (no server‑side session store required) and built‑in expiration handling.

Understanding OAuth2

OAuth2 is an authorization framework that enables third‑party applications to obtain limited access to an HTTP service. In the context of FastAPI, we typically use the OAuth2PasswordBearer flow, which combines username/password authentication with JWT token issuance. This flow works well for single‑page applications (SPAs), mobile apps, and microservices.

Step‑by‑Step Implementation

1. Set Up the Project

First, create a virtual environment and install the required packages:

python -m venv venv
source venv/bin/activate  # On Windows use `venv\Scripts\activate`
pip install fastapi uvicorn python‑jose[cryptography] passlib[bcrypt] pydantic

2. Define Settings and Security Utilities

Store secret keys and token parameters in a dedicated settings.py file. Keeping secrets out of source code is essential for security and SEO‑friendly documentation.

# settings.py
import os
from datetime import timedelta

SECRET_KEY = os.getenv("SECRET_KEY", "supersecretkey")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
REFRESH_TOKEN_EXPIRE_DAYS = 7

3. Create Pydantic Models

These models define the shape of data for users, token payloads, and authentication requests.

# schemas.py
from pydantic import BaseModel, EmailStr
from datetime import datetime

class UserBase(BaseModel):
    email: EmailStr

class UserCreate(UserBase):
    password: str

class UserInDB(UserBase):
    hashed_password: str
    is_active: bool = True

class Token(BaseModel):
    access_token: str
    token_type: str

class TokenData(BaseModel):
    sub: str | None = None
    exp: datetime | None = None

4. Implement Password Hashing

Never store plain‑text passwords. Use passlib with bcrypt for strong hashing.

# security.py
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password: str) -> str:
    return pwd_context.hash(password)

5. Create JWT Utility Functions

These functions generate and decode JWTs, handling expiration and signature verification.

# token.py
from datetime import datetime, timedelta
from jose import JWTError, jwt
from .settings import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES

def create_access_token(data: dict, expires_delta: timedelta | None = None) -> str:
    to_encode = data.copy()
    expire = datetime.utcnow() + (expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

def decode_token(token: str) -> dict:
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except JWTError:
        raise

6. Build the FastAPI App with OAuth2 Dependency

Now we tie everything together. The OAuth2PasswordBearer dependency extracts the token from the Authorization: Bearer header.

# main.py
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from .schemas import Token, UserCreate, UserInDB, TokenData
from .security import verify_password, get_password_hash
from .token import create_access_token, decode_token

app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# In‑memory "database" for demo purposes
fake_users_db = {}

def get_user(email: str) -> UserInDB | None:
    user = fake_users_db.get(email)
    return UserInDB(**user) if user else None

def authenticate_user(email: str, password: str) -> UserInDB | None:
    user = get_user(email)
    if not user or not verify_password(password, user.hashed_password):
        return None
    return user

async def get_current_user(token: str = Depends(oauth2_scheme)) -> UserInDB:
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = decode_token(token)
        email: str = payload.get("sub")
        if email is None:
            raise credentials_exception
        token_data = TokenData(sub=email)
    except JWTError:
        raise credentials_exception
    user = get_user(email=token_data.sub)
    if user is None:
        raise credentials_exception
    return user

@app.post("/register", response_model=Token, tags=["auth"])
def register(user: UserCreate):
    if user.email in fake_users_db:
        raise HTTPException(status_code=400, detail="Email already registered")
    hashed_password = get_password_hash(user.password)
    fake_users_db[user.email] = {"email": user.email, "hashed_password": hashed_password}
    access_token = create_access_token(data={"sub": user.email})
    return {"access_token": access_token, "token_type": "bearer"}

@app.post("/token", response_model=Token, tags=["auth"])
def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=400, detail="Incorrect email or password")
    access_token = create_access_token(data={"sub": user.email})
    return {"access_token": access_token, "token_type": "bearer"}

@app.get("/users/me", tags=["users"])
def read_current_user(current_user: UserInDB = Depends(get_current_user)):
    return {"email": current_user.email, "is_active": current_user.is_active}

7. Run the Application

Start the server with Uvicorn and explore the automatically generated OpenAPI docs at /docs:

uvicorn main:app --reload

Best Practices for Production‑Ready JWT OAuth2

  • Rotate secrets regularly – Store SECRET_KEY in a vault or environment variable, not in source control.
  • Use HTTPS exclusively – Prevent token interception.
  • Implement token revocation – Store a blacklist or use short‑lived access tokens with refresh tokens.
  • Set appropriate scopes – Limit what each token can do; FastAPI’s SecurityScopes makes this easy.
  • Validate audience and issuer – Add aud and iss claims when integrating with third‑party identity providers.
  • Monitor and log authentication events – Detect brute‑force attempts and abnormal usage patterns.

Extending the Example: Refresh Tokens and Social Login

While the basic flow covers most internal applications, real‑world projects often need additional features:

  1. Refresh Tokens – Issue a long‑lived refresh token that can request a new access token without re‑entering credentials.
  2. OAuth2 Authorization Code Flow – Integrate with providers like Google, GitHub, or Azure AD for single‑sign‑on (SSO).
  3. Role‑Based Access Control (RBAC) – Embed user roles in the JWT payload and enforce them with FastAPI dependencies.
  4. Rate Limiting – Combine fastapi-limiter with JWT claims to protect endpoints from abuse.

Testing Your Authentication Layer

Automated tests ensure that security never regresses. Use pytest and FastAPI’s TestClient to simulate login, token refresh, and

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *