FastAPI Security Authentication
FastAPI has built-in security tools that support common authentication and authorization methods such as OAuth2, JWT tokens, and API keys. This section introduces how to implement user authentication based on OAuth2 + JWT.
Security Authentication Overview
Security schemes supported by FastAPI:
| Solution | Applicable scenarios | Description |
|---|---|---|
| HTTP Basic Auth | Simple Internal Service | Username and password are encoded in the request header, which has lower security |
| API Key | Inter-service call | Pass the key via request headers, query parameters, or cookies |
| OAuth2 + JWT | Front-end/Back-end Separated Application | The most common approach, secure and flexible |
OAuth2 Password Mode + JWT
This is the most commonly used authentication scheme for front-end and back-end separated applications. Flow:
- The client sends the username and password to
/tokenEndpoint - The server verifies the credentials and returns a JWT access token
- The client carries the token in subsequent requests (
Authorization: Bearer <token>) - The server verifies the token to identify the user
1. Install Dependencies
pip install "python-jose[cryptography]" passlib[bcrypt]
2. Complete Example
Example
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
from pydantic import BaseModel
# ===== Configuration =====
SECRET_KEY = "your-secret-key-keep-it-secret" # Use environment variables in production
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# ===== Password Hashing =====
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# ===== OAuth2 Scheme =====
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# ===== Data Models =====
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: str | None = None
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = None
class UserInDB(User):
hashed_password: str
# ===== Mock database =====
fake_users_db = {
"alice": {
"username": "alice",
"full_name": "Alice Wonderson",
"email": "alice@example.com",
"hashed_password": pwd_context.hash("secret"), # Password: secret
"disabled": False,
}
}
# ===== Utility Functions =====
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""Validate password"""
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str) -> str:
"""Generate password hash"""
return pwd_context.hash(password)
def get_user(db: dict, username: str) -> UserInDB | None:
"""Get user from database"""
if username in db:
return UserInDB(**db[username])
return None
def authenticate_user(db: dict, username: str, password: str):
"""Verify user credentials"""
user = get_user(db, username)
if not user:
return False
if not verify_password(password, user.hashed_password):
return False
return user
def create_access_token(data: dict, expires_delta: timedelta | None = None):
"""Create JWT access token"""
to_encode = data.copy()
expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
"""Get current user from token (dependency function)"""
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(fake_users_db, username)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(
current_user: Annotated[User, Depends(get_current_user)],
):
"""Get current active user"""
if current_user.disabled:
raise HTTPException(status_code=400, detail=User has been disabled)
return current_user
# ===== Routes =====
app = FastAPI()
@app.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
"""Login to obtain token"""
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me")
async def read_users_me(
current_user: Annotated[User, Depends(get_current_active_user)],
):
"""Get current user info (authentication required)"""
return current_user
@app.get("/users/me/items")
async def read_own_items(
current_user: Annotated[User, Depends(get_current_active_user)],
):
"""Get the current user's items (requires authentication)"""
return [{"item_id": "Foo", "owner": current_user.username}]
Code explanation
Password hash
UsagepasslibThe bcrypt algorithm hashes passwords to ensure that plaintext passwords are not stored in the database:
| Function | Description |
|---|---|
pwd_context.hash(password) | Convert the plaintext password into a hash value |
pwd_context.verify(plain, hashed) | Verify whether the plaintext password matches the hash value |
JWT Token
JWT (JSON Web Token) is a secure token format that contains user information and expiration time:
| Operation | Function | Description |
|---|---|---|
| Create token | jwt.encode() | Encode the data into a JWT string |
| Parse token | jwt.decode() | Parse and verify the JWT string |
| Set expiration | "exp": expire | Token Expiration Time |
| Store User Identifier | "sub": username | The subject of the token (usually the username) |
OAuth2PasswordBearer
Tell FastAPI fromAuthorization: Bearer <token>Get the token from the request header:
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
tokenUrl="token"Specifies the endpoint path for the client to obtain the token, which will appear in the API documentation.
SECRET_KEYIt must be kept secret and be sufficiently complex; in production environments, environment variables should be used for storage. If the secret key is leaked, attackers can forge tokens for any user.
Test using the API documentation
After configuring OAuth2, the Swagger UI will display an "Authorize" button:
- Click"Authorize"
- Enter the username and password (e.g.
alice/secret) - Click"Authorize"Get token
- All subsequent requests will automatically carry the token
Summary
- OAuth2 + JWT is the most commonly used authentication scheme for front-end and back-end separated applications
- Passwords must be hashed for storage and must not be stored in plaintext
- JWT tokens contain user identifiers and expiration time
- Using Dependency Injection (
Depends) to implement the reuse of authentication logic - In production environments, the SECRET_KEY must be kept secret