Python FastAPI JWT OAuth2 and Bearer Authentication
python fastapi jwt oauth2 and bearer authentication: Learn how to implement OAuth2 password flow with JWT in FastAPI, including token generation, bearer token dependen...
Implementing OAuth2 with JWT in FastAPI gives you a stateless authentication mechanism that works well with bearer tokens. This article walks through the standard password flow, token generation, and route protection using python fastapi jwt oauth2 and bearer authentication.
How OAuth2 and JWT Fit Together in FastAPI
FastAPI's security utilities implement the OAuth2 specification, but they don't enforce a specific token format. You can combine OAuth2's password flow with JWT to get a compact, self-contained token that carries claims and can be verified without server-side session storage.
The flow works like this: the client sends a username and password to a token endpoint. The server validates those credentials, creates a JWT containing the user identity and an expiration time, and returns it. The client then sends that token in the Authorization header as Bearer <token> for subsequent requests. FastAPI's dependency injection system makes it straightforward to protect routes by requiring a valid token and optionally loading the current user.
Setting Up the Required Dependencies
You need a few libraries beyond FastAPI itself. The typical stack includes:
python-jose[cryptography]for creating and verifying JWTs.passlib[bcrypt]for password hashing.python-multipartbecause the OAuth2 password form sends data asapplication/x-www-form-urlencoded.
Install them with pip:
pip install fastapi python-jose[cryptography] passlib[bcrypt] python-multipart
These libraries are widely used and well maintained. The exact versions matter less than the APIs, which have been stable for several years.
Hashing Passwords with Passlib
Before you can issue tokens, you need a way to verify passwords. Storing plaintext passwords is never acceptable. Passlib provides a consistent interface for hashing and verification, and bcrypt is a solid choice for the algorithm.
Create a utility module for password handling:
from passlib.context import CryptContext pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def hash_password(password: str) -> str: return pwd_context.hash(password) def verify_password(plain_password: str, hashed_password: str) -> bool: return pwd_context.verify(plain_password, hashed_password)
The CryptContext object manages the hashing scheme. Using deprecated="auto" means it will automatically warn if you ever switch to a newer algorithm, but it will still verify old hashes.
Creating the OAuth2 Password Flow Token Endpoint
FastAPI provides OAuth2PasswordRequestForm as a dependency that parses the standard OAuth2 form fields: username, password, and optional scope. You'll use this in a token endpoint that validates the credentials and returns a JWT.
Here's a minimal token endpoint:
from datetime import datetime, timedelta from typing import Optional from fastapi import Depends, FastAPI, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from jose import JWTError, jwt SECRET_KEY = "your-secret-key" ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 app = FastAPI() def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta(minutes=15) to_encode.update({"exp": expire}) return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) @app.post("/token") async def login(form_data: OAuth2PasswordRequestForm = Depends()): user = fake_authenticate_user(form_data.username, form_data.password) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"}, ) access_token = create_access_token(data={"sub": user.username}) return {"access_token": access_token, "token_type": "bearer"}
The sub claim is a standard JWT claim that represents the subject, usually the user identifier. The exp claim sets the expiration time. The WWW-Authenticate header is required by the OAuth2 spec when authentication fails.
Building the Bearer Token Dependency
To protect routes, you need a dependency that extracts the token from the Authorization header, verifies it, and returns the current user. FastAPI's OAuth2PasswordBearer handles the header parsing for you.
Define the security scheme and the dependency:
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") def get_current_user(token: str = Depends(oauth2_scheme)): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") if username is None: raise credentials_exception except JWTError: raise credentials_exception user = get_user_by_username(username) if user is None: raise credentials_exception return user
The tokenUrl parameter tells the OpenAPI documentation where the token endpoint is. The dependency will automatically parse the Authorization: Bearer <token> header and pass the token to your function.
Protecting Routes with the Current User Dependency
Now you can protect any route by adding the get_current_user dependency. FastAPI will run it before the endpoint and inject the user object into the function.
@app.get("/users/me") async def read_users_me(current_user: User = Depends(get_current_user)): return current_user
If the token is missing or invalid, FastAPI will return a 401 response automatically. If you need to enforce roles or permissions, you can inspect the user object or include additional claims in the JWT.
Handling Token Expiration and Refresh
JWTs are stateless, so you can't revoke them before they expire. The standard approach is to keep access tokens short-lived and issue a refresh token that can be used to obtain new access tokens. The refresh token is typically stored in a database and can be revoked.
A simple refresh flow adds a second token endpoint:
@app.post("/refresh") async def refresh_token(refresh_token: str = Depends(oauth2_scheme)): # Verify the refresh token, then issue a new access token # This is a simplified example; production code should validate # the refresh token against a database or allowlist. payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=[ALGORITHM]) username = payload.get("sub") if username is None: raise HTTPException(status_code=401, detail="Invalid refresh token") new_access_token = create_access_token(data={"sub": username}) return {"access_token": new_access_token, "token_type": "bearer"}
In practice, you should use a separate secret or at least a different aud claim for refresh tokens, and store them server-side so you can revoke them if a user logs out.
Security Considerations for JWT in FastAPI
The most critical decision is the SECRET_KEY. It must be long, random, and kept secret. If it leaks, anyone can forge tokens. Store it in an environment variable or a secrets manager, never in source control.
The algorithm choice matters. HS256 is symmetric and requires the same secret for signing and verification. RS256 uses a private key to sign and a public key to verify, which is better when multiple services need to verify tokens without sharing a secret. FastAPI's python-jose supports both.
Token expiration is not optional. Without an exp claim, a stolen token works forever. Set a reasonable lifetime based on your application's risk profile. For highly sensitive operations, consider short-lived tokens combined with a refresh mechanism.
Always validate the token on every request. The get_current_user dependency should decode and verify the signature, check the expiration, and confirm the subject exists in your user store. Never trust a token without verifying it.
Finally, use HTTPS in production. JWTs are sent in the Authorization header, and if that header is transmitted over plain HTTP, an attacker can capture it. TLS protects the token in transit, but it doesn't protect against token theft from the client side, so keep tokens out of logs and browser storage where possible.