Security: OAuth2 Password Flow

The OAuth2 "password flow" (Resource Owner Password Credentials) is the classic username + password login for APIs: the client posts credentials to a /token endpoint and receives a bearer token to send on every subsequent request in the Authorization header.

The Ingredients

pip install "passlib[bcrypt]"
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from passlib.context import CryptContext

app = FastAPI()

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

Hashing and Verifying Passwords

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

def verify_password(plain: str, hashed: str) -> bool:
    return pwd_context.verify(plain, hashed)

verify extracts the salt from the stored hash and compares in constant time. Registration stores only the hash; login verifies the plaintext against it.

The /token Endpoint

fake_users_db = {
    "alice": {
        "username": "alice",
        "hashed_password": hash_password("secret123"),
    }
}

@app.post("/token")
def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = fake_users_db.get(form_data.username)
    if not user or not verify_password(form_data.password, user["hashed_password"]):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return {"access_token": form_data.username, "token_type": "bearer"}

Protecting Endpoints

def get_current_user(token: str = Depends(oauth2_scheme)):
    if token not in [u for u in fake_users_db]:
        raise HTTPException(status_code=401, detail="Invalid token")
    return fake_users_db[token]

@app.get("/users/me")
def read_me(user: dict = Depends(get_current_user)):
    return {"username": user["username"]}

The chain is: read_me → get_current_user → oauth2_scheme. FastAPI drew this in the docs and enforced it on every request — and in production the "token" would be a JWT (next page).

Quick Questions and Answers

Q: Why form data instead of JSON in the /token endpoint?

A: The OAuth2 spec defines the token request as form-encoded so it matches how browsers/CLIs submit logins. FastAPI's docs UI and every OAuth2 client library follow the spec — a JSON login endpoint would be off-spec (you can still add one in parallel for your own frontend).

Q: Where is the "Bearer token" actually stored in this flow?

A: Nowhere on the server — that is the point of stateless auth. The server hands the client an opaque (or signed JWT) string; for each request the client sends it back, and the server validates it without keeping a session store. (Session-based auth with cookies is the stateful alternative.)

Q: Is the password flow still recommended in OAuth2.1?

A: It is deprecated for third-party clients (Authorization Code + PKCE is preferred), but for a first-party API consumed by your own frontend or CLI it remains the simplest pragmatic choice, and it is what FastAPI's official tutorial uses.

Conclusion

Three pieces build full OAuth2 password login: a hash utility (passlib), a /token endpoint returning an access_token, and OAuth2PasswordBearer protecting routes. Everything else — JWT content, scopes, refresh — layers on top of this skeleton.