FastAPI Authentication — JWT and OAuth2 with Password Flow
OAuth2PasswordBearer class, which acts as a dependency to extract the Bearer token from the 'Authorization' header. Once extracted, you use python-jose to decode the JWT and verify the 'sub' claim. Protecting routes is as simple as adding Depends(get_current_user) to your path operation parameters, ensuring that unauthenticated requests are rejected with a 401 Unauthorized status before the logic executes.
The Authentication Backbone: JWT Configuration
Before handling requests, we must define our security constants. In a real-world scenario at TheCodeForge, we never hardcode secrets. We use environment variables to manage the SECRET_KEY and ALGORITHM. The OAuth2PasswordBearer is initialized here to tell FastAPI where the login 'token' endpoint is located.
import os from datetime import datetime, timedelta, timezone from typing import Annotated from fastapi import Depends, FastAPI, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from jose import JWTError, jwt from passlib.context import CryptContext # Configuration - In production, use os.getenv('SECRET_KEY') SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") app = FastAPI()
Token Generation and Login Flow
The /token endpoint is the gateway. It receives the OAuth2PasswordRequestForm, verifies the user's identity, and returns a signed JWT. This token contains a 'payload' (usually the username) and an 'expiration' time to prevent replay attacks.
def create_access_token(data: dict, expires_delta: timedelta | None = None): to_encode = data.copy() if expires_delta: expire = datetime.now(timezone.utc) + expires_delta else: expire = datetime.now(timezone.utc) + timedelta(minutes=15) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt @app.post("/token") async def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]): # Mock user verification - Replace with DB call if form_data.username != "forge_dev" or form_data.password != "secret_pass": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"}, ) access_token = create_access_token(data={"sub": form_data.username}) return {"access_token": access_token, "token_type": "bearer"}
Protecting Routes with Dependency Injection
To secure a route, we create a dependency that decodes the token. If the jwt.decode fails (token expired or tampered with), it raises an HTTPException. If successful, it returns the user object, which is then available to your route function.
async def get_current_user(token: Annotated[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 # Return a user dict or DB model return {"username": username, "active": True} @app.get("/users/me") async def read_users_me(current_user: Annotated[dict, Depends(get_current_user)]): return current_user
🎯 Key Takeaways
- OAuth2PasswordBearer is a built-in provider that extracts the token from the 'Authorization: Bearer
' header automatically. - The 'sub' (subject) claim in the JWT payload should uniquely identify the user (e.g., username or UUID).
- Always use 'Annotated' for dependencies in modern FastAPI for better editor support and type safety.
- Access tokens should be short-lived; use Refresh Tokens if users need to stay logged in for long periods without re-entering passwords.
- Security is only as good as your SECRET_KEY—keep it high-entropy and strictly out of version control.
Interview Questions on This Topic
- QExplain the 'Stateless' nature of JWT. Why does this benefit FastAPI's performance compared to Session-based auth?
- QHow does the `Depends()` mechanism help in preventing code duplication for protected routes?
- QScenario: A user's laptop is stolen. If we use standard JWTs, how would you 'blacklist' or revoke their current active token before it expires?
- QWhat is the risk of using a weak `SECRET_KEY`, and how does it compromise the 'signature' part of the JWT?
- QDescribe the flow of a 'Bearer' token from the client to the server. Which HTTP header is used?
Frequently Asked Questions
What is the difference between authentication and authorisation?
Authentication (AuthN) is the process of verifying that a user is who they say they are—essentially verifying their digital ID. Authorisation (AuthZ) happens after authentication and determines what specific resources or actions that verified user is permitted to access. In FastAPI, get_current_user usually handles AuthN, while scopes or role-checks handle AuthZ.
How do I implement refresh tokens in FastAPI?
To implement refresh tokens, you issue two tokens at login: an access_token (expires in minutes) and a refresh_token (expires in days/weeks). You create a /refresh endpoint that accepts the refresh token, validates it against your database (to ensure it hasn't been revoked), and issues a brand new access token. This allows for a seamless user experience while keeping the 'working' token's lifespan very short.
Is python-jose still the recommended library for FastAPI JWT?
While python-jose is widely used in documentation, many developers are moving to PyJWT or Authlib due to better maintenance. However, for standard OAuth2 implementation, python-jose remains the primary choice for FastAPI compatibility. Ensure you install with python-jose[cryptography] for secure signing.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.