FastAPI OAuth2 and Password Flow Authentication

Authentication answers the question: "Who are you?" Before your API gives access to protected data, it must verify the caller's identity. OAuth2 Password Flow is the most common pattern for username/password login in FastAPI applications.

How OAuth2 Password Flow Works

Step 1: User sends username + password to /token endpoint
         │
         ▼
Step 2: Server verifies credentials against database
         │
         ▼
Step 3: Server returns an access token (a long random string)
         │
         ▼
Step 4: User sends token in Authorization header for all future requests
         │
         ▼
Step 5: Server reads token, identifies user, allows or denies access

Install Password Hashing Library

pip install "passlib[bcrypt]"

Never store plain text passwords. Always store a hashed version. bcrypt is the industry standard hashing algorithm for passwords.

Setting Up Password Hashing

from passlib.context import CryptContext

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

def hash_password(plain: str) -> str:
    return pwd_context.hash(plain)

def verify_password(plain: str, hashed: str) -> bool:
    return pwd_context.verify(plain, hashed)
Plain password:  "mySecret123"
Hashed version:  "$2b$12$KIX3..."  (60+ chars, different every time)

verify_password("mySecret123", "$2b$12$KIX3...") → True
verify_password("wrongPass",   "$2b$12$KIX3...") → False

The Login Endpoint

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

app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# Fake database for this example
fake_users = {
    "meera": {"username": "meera", "hashed_password": hash_password("secret123")}
}

@app.post("/token")
def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = fake_users.get(form_data.username)
    if not user:
        raise HTTPException(status_code=401, detail="Incorrect username")
    if not verify_password(form_data.password, user["hashed_password"]):
        raise HTTPException(status_code=401, detail="Incorrect password")

    return {"access_token": form_data.username, "token_type": "bearer"}

OAuth2PasswordRequestForm reads username and password from form data — which is the standard OAuth2 format for the token endpoint.

Reading the Token on Protected Routes

@app.get("/me")
def get_current_user(token: str = Depends(oauth2_scheme)):
    user = fake_users.get(token)
    if user is None:
        raise HTTPException(status_code=401, detail="Invalid token")
    return {"username": user["username"]}
Client request:
  GET /me
  Authorization: Bearer meera

FastAPI extracts "meera" from the header
→ looks it up in fake_users
→ returns user info

What oauth2_scheme Does

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

It does two things:
  1. Tells the /docs page where the login form is (tokenUrl="token")
  2. Extracts the Bearer token from the Authorization header
     and passes it to your function as a string

The /docs Login Button

With OAuth2PasswordBearer configured, the Swagger UI at /docs shows an "Authorize" button. Click it, enter username and password, and the docs page sends the token automatically with every test request. This makes manual API testing much easier.

Token Storage on the Client Side

Web app (browser):
  Store token in memory (safest) or localStorage.
  Send in every request header:
    Authorization: Bearer eyJhbGc...

Mobile app:
  Store in secure storage (Keychain on iOS, Keystore on Android).
  Same header format.

Key Points

  • OAuth2 Password Flow uses username + password to get a token, then the token for all future requests.
  • Always hash passwords with bcrypt — never store plain text.
  • Use OAuth2PasswordRequestForm to read login credentials from form data.
  • Use OAuth2PasswordBearer to extract the Bearer token from request headers.
  • The next topic covers JWT tokens, which make the token itself carry user information securely.

Leave a Comment

Your email address will not be published. Required fields are marked *