Last modified: Dec 02, 2025 By Alexander Williams

FastAPI OAuth2 Password Flow Security Guide

Security is vital for modern web APIs. FastAPI makes it easy to add robust authentication.

This guide covers the OAuth2 password flow. It is a common method for user login.

You will learn to secure endpoints and manage user sessions with JSON Web Tokens.

What is OAuth2 Password Flow?

OAuth2 is an authorization framework. It allows apps to access user data securely.

The password flow is suitable for trusted applications. Users provide credentials directly.

FastAPI has built-in OAuth2 support. It handles many security details for you.

Project Setup

First, create a virtual environment. Then install the required packages.


pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt] python-multipart

These packages provide core security functions. They handle tokens and password hashing.

Creating Pydantic Models

Define your data structures with Pydantic. This ensures validation and type safety.


from pydantic import BaseModel
from typing import Optional

class User(BaseModel):
    username: str
    email: Optional[str] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None

class UserInDB(User):
    hashed_password: str

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

class TokenData(BaseModel):
    username: Optional[str] = None

Models define the shape of user data and tokens. They are essential for FastAPI Validation with Pydantic Models.

Hashing User Passwords

Never store plain text passwords. Always use a strong hashing algorithm.


from passlib.context import CryptContext

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

def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password):
    return pwd_context.hash(password)

The CryptContext manages password hashing. The verify_password function checks credentials.

The get_password_hash function creates a secure hash for storage.

Simulating a User Database

For this example, we use a fake in-memory database. A real app needs a persistent store.


fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "[email protected]",
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", # "secret"
        "disabled": False,
    }
}

def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)

def authenticate_user(fake_db, username: str, password: str):
    user = get_user(fake_db, username)
    if not user:
        return False
    if not verify_password(password, user.hashed_password):
        return False
    return user

The get_user function retrieves a user by username. The authenticate_user function validates login attempts.

Creating JWT Access Tokens

JSON Web Tokens (JWT) are used for session management. They are signed and contain user data.


from datetime import datetime, timedelta
from jose import JWTError, jwt

SECRET_KEY = "your-secret-key-here-change-in-production"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

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})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

The create_access_token function generates a signed JWT. It includes an expiration time for security.

For a deeper dive, see our FastAPI JWT Authentication Python Tutorial.

Building the FastAPI Application

Now, integrate all components into the main FastAPI app. Define the login endpoint.


from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(fake_users_db, 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_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"}

The OAuth2PasswordBearer class defines the token endpoint. The login_for_access_token path operation handles user login.

It uses FastAPI Dependency Injection Best Practices for the form data.

Protecting API Endpoints

Create a dependency to get the current user from a token. Use it to secure routes.


async 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
        token_data = TokenData(username=username)
    except JWTError:
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    return user

async def get_current_active_user(current_user: User = Depends(get_current_user)):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user

@app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user

The get_current_user dependency decodes and validates the JWT. The get_current_active_user dependency checks if the user is active.

The read_users_me endpoint is now fully protected. It only returns data for authenticated, active users.

Testing the API

Run the server with Uvicorn. Then test the login and protected endpoints.


uvicorn main:app --reload

First, obtain a token by sending a POST request to /token.


curl -X POST "http://127.0.0.1:8000/token" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d "username=johndoe&password=secret"

Expected output:


{"access_token":"eyJhbGciOiJIUzI1NiIs...","token_type":"bearer"}

Now, use the token to access the protected endpoint.


curl -X GET "http://127.0.0.1:8000/users/me/" \
     -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Expected output:


{"username":"johndoe","email":"[email protected]","full_name":"John Doe","disabled":false}

For comprehensive testing, learn about Test FastAPI Endpoints with pytest and TestClient.

Next Steps and Best Practices

This example uses a fake database. A real application needs a proper database setup.

Consider using FastAPI Database Migrations with Alembic to manage schema changes.

Always use environment variables for secrets like SECRET_KEY. Never hardcode them.

Increase token security with shorter expiration times. Implement refresh token logic.

For production deployment, follow our guide on Deploy FastAPI with Docker for Production.

Conclusion

FastAPI provides excellent tools for OAuth2 security. The password flow is straightforward to implement.

You learned to hash passwords, create JWTs, and protect endpoints. Always follow security best practices.

This foundation helps you build secure and reliable APIs. Your users' data will be safe.