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:

SolutionApplicable scenariosDescription
HTTP Basic AuthSimple Internal ServiceUsername and password are encoded in the request header, which has lower security
API KeyInter-service callPass the key via request headers, query parameters, or cookies
OAuth2 + JWTFront-end/Back-end Separated ApplicationThe 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:

  1. The client sends the username and password to/tokenEndpoint
  2. The server verifies the credentials and returns a JWT access token
  3. The client carries the token in subsequent requests (Authorization: Bearer <token>)
  4. The server verifies the token to identify the user

1. Install Dependencies

pip install "python-jose[cryptography]" passlib[bcrypt]

2. Complete Example

Example

from datetime import datetime, timedelta
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:

FunctionDescription
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:

OperationFunctionDescription
Create tokenjwt.encode()Encode the data into a JWT string
Parse tokenjwt.decode()Parse and verify the JWT string
Set expiration"exp": expireToken Expiration Time
Store User Identifier"sub": usernameThe 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:

  1. Click"Authorize"
  2. Enter the username and password (e.g.alice / secret)
  3. Click"Authorize"Get token
  4. 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
other extensions