Home/Learn/FastAPI/OAuth2 Password Flow — Login That Issues Tokens

OAuth2 Password Flow — Login That Issues Tokens

Intermediate
Auth & Security

OAuth2PasswordRequestForm receives username/password at /token, the app returns a bearer token, and OAuth2PasswordBearer extracts it on every protected call — the login skeleton behind the Swagger Authorize button.

Overview

APIs authenticate with tokens, not server-side sessions: the client logs in once at a token endpoint, receives an access token, and sends it as Authorization: Bearer <token> on every request — stateless, so any instance behind the load balancer can verify it. FastAPI ships the two halves of this contract: OAuth2PasswordRequestForm parses the standard form-encoded login body, and OAuth2PasswordBearer is a dependency that pulls the bearer token off incoming requests (returning 401 with the correct WWW-Authenticate header when absent). Declaring them also lights up the Authorize button in /docs, so the whole team can log in and test protected endpoints from the browser. This chapter wires the skeleton with a fake token; the next two chapters supply the real hashing and real JWTs.

The /token Endpoint + the Bearer Extractor

tokenUrl tells the docs where login lives. The form is form-encoded (username/password fields — the OAuth2 spec), not JSON. Wrong credentials return 401, never "wrong password" specifics.

Form in, {access_token, token_type} out, Bearer on every call
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

app = FastAPI()

# Extracts "Authorization: Bearer xyz" → "xyz"; 401 if header missing
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

FAKE_DB = {"asha@nitk.edu.in": {"name": "Asha", "hashed": "bcrypt$..."}}

@app.post("/token")
def login(form: OAuth2PasswordRequestForm = Depends()):
    user = FAKE_DB.get(form.username)          # form fields: username, password
    password_ok = user is not None             # verify_password() → next chapter
    if not user or not password_ok:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="incorrect username or password",   # deliberately vague:
            headers={"WWW-Authenticate": "Bearer"},    # never reveal WHICH was wrong
        )
    return {
        "access_token": "fake-token-" + form.username,   # real JWT → next chapter
        "token_type": "bearer",                          # spec-required shape
    }

@app.get("/profile")
def profile(token: str = Depends(oauth2_scheme)):
    return {"your_token": token}     # decoding it comes next

# The wire format:
#   POST /token   Content-Type: application/x-www-form-urlencoded
#   username=asha%40nitk.edu.in&password=s3cret
#   → {"access_token": "...", "token_type": "bearer"}
#   GET /profile  Authorization: Bearer <access_token>

Why Tokens (Not Sessions) — and What /docs Gives You

Stateless verification is what lets APIs scale horizontally and serve mobile + SPA + server clients uniformly. And because the scheme is declared, Swagger UI handles the whole login dance for manual testing.

Stateless auth scales; /docs Authorize does the ritual for you
# Sessions vs tokens for APIs:
#
#   Cookie sessions                     Bearer tokens
#   ───────────────────                 ─────────────────────
#   state lives server-side            state lives IN the token (signed)
#   sticky sessions / shared store     any instance verifies independently
#   browser-centric (cookies)          browsers, apps, curl, services alike
#   CSRF protection needed             no cookies → no classic CSRF
#
# (Server-rendered websites still legitimately use cookie sessions —
#  YesStudy-style SPAs + mobile apps are why APIs standardised on bearer.)

# What declaring OAuth2PasswordBearer buys you in /docs:
#   1. A padlock icon on every protected endpoint
#   2. The "Authorize" button → posts to tokenUrl ("token")
#   3. Swagger stores the token and attaches Authorization: Bearer
#      to every "Try it out" call — the whole team tests auth flows
#      without curl or Postman.

# Two rules already visible in this skeleton:
#   - 401 responses include WWW-Authenticate: Bearer (the HTTP spec says so;
#     FastAPI's helpers do it — keep it when raising manually)
#   - Login errors stay vague. "User not found" vs "wrong password"
#     tells an attacker which emails are registered (user enumeration).

Key Points to Remember

  • 1OAuth2PasswordRequestForm parses the form-encoded login; response is {access_token, token_type}
  • 2OAuth2PasswordBearer extracts the bearer token and 401s when it is missing
  • 3Tokens are stateless — any instance verifies; no sticky sessions, no CSRF cookies
  • 4Login failures stay vague (no user enumeration) and carry WWW-Authenticate: Bearer

Interview Questions

Sign in to ask Aria
1

Why do APIs prefer bearer tokens over cookie sessions? When are sessions still right?

MediumZomato
2

What is user enumeration and how does your login endpoint avoid it?

MediumCRED
3

Walk through what happens in Swagger UI when you click Authorize.

EasyPostman

Ask Aria about OAuth2 Password Flow — Login That Issues Tokens

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…