Security and Authentication
Learn to implement robust security and authentication mechanisms to protect your FastAPI applications.
Content
Using OAuth2 with Password
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
OAuth2 with Password (the "I Know My Password, Give Me a Token" Flow)
You already met Basic Auth and flirted with OAuth2 + JWT. Now we're taking the next logical (and slightly spicy) step: using the OAuth2 password grant with FastAPI — the flow where the app takes a username/password and trades it for a token.
This lesson builds on the previous topics:
- You know how to hash/verify passwords and why Basic Auth is insufficient for modern apps.
- You know what JWTs are and why we sign/expire them.
- You used dependency injection to provide a DB session or reusable services.
We're going to weave all that together into a practical, secure-ish implementation, and point out where to be extra cautious.
TL;DR (because we all skim first)
- The client sends username/password to a token endpoint.
- Server verifies credentials, returns an access token (often a signed JWT).
- Protected routes depend on an OAuth2 bearer token dependency to get the current user.
- Use HTTPS. Use hashed passwords. Prefer other grants for third-party login.
Why use the password grant? (and why not)
- Good when your own first-party frontend and API are tightly coupled (e.g., official mobile app).
- Bad when a third-party should authenticate — exposing user passwords to third parties is a non-starter.
Security soapbox: The password grant is convenient but risky if mishandled. Use HTTPS, short-lived tokens, refresh tokens, and consider more secure flows (Authorization Code + PKCE) for public clients.
The components (fast checklist)
- Token endpoint: accepts form data (username & password) — FastAPI uses OAuth2PasswordRequestForm for this.
- Password verification: compare submitted password against hashed password in DB (use passlib/bcrypt).
- Token creation: sign a JWT with expiry (we did JWT in the previous topic — reuse that logic).
- Dependency that extracts and validates token (OAuth2PasswordBearer) and returns current_user.
Example: Minimal but Practical Implementation
This assumes you already have:
- A function to get a DB/user (e.g., get_user(username)).
- Password hashing utilities (verify_password, get_password_hash).
- JWT encode/decode helpers using python-jose or PyJWT (create_access_token, decode_token).
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import jwt, JWTError
from datetime import datetime, timedelta
app = FastAPI()
# token URL is the endpoint path where clients will send credentials
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
SECRET_KEY = "super-secret-key" # replace with environment variable
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# Fake DB for demo
fake_users_db = {
"alice": {"username": "alice", "hashed_password": "fakehashedsecret", "disabled": False}
}
# helper to create JWT
def create_access_token(data: dict, expires_delta: timedelta | None = None):
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)
# token endpoint
@app.post("/token")
async def login_for_access_token(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"},
)
access_token = create_access_token(data={"sub": user["username"]}, expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
return {"access_token": access_token, "token_type": "bearer"}
# dependency to get current user from token
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
except JWTError:
raise credentials_exception
user = fake_users_db.get(username)
if user is None:
raise credentials_exception
return user
@app.get("/users/me")
async def read_users_me(current_user: dict = Depends(get_current_user)):
return current_user
Note: verify_password and get_password_hash should come from passlib (bcrypt). tokenUrl must match the actual path where credentials are posted.
Scopes — giving tokens permission levels
OAuth2 supports "scopes"—tiny labels that tell the API what the token is allowed to do.
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token", scopes={"items:read": "Read your items"})
# then in get_current_user you would inspect token payload for 'scopes'
# and verify the requested endpoint requires that scope
Use scopes to avoid "all-powerful" tokens when you just need to read public data.
Dependency Injection: Where it shines here
- The token creation and verification lives as reusable functions.
Depends(oauth2_scheme)turns the raw header into a token string;get_current_useris a DI hook that fetches & validates the user.- You can inject DB sessions, caching, or revoked-token checkers into the same dependency chain.
Think of DI like a kitchen: you don't want to cook every dish with a different stove. You want a reliable stove (dependency) you can plug into recipes.
Pitfalls & Good Practices
- Always use HTTPS. No debating this.
- Never log passwords. Even in dev, don't log them.
- Short token lifetimes + refresh tokens for prolonged sessions.
- Consider refresh token revocation / rotation.
- Prefer Authorization Code + PKCE for public clients (mobile/web) interacting with third-party services.
Expert take: "If someone asks your app to send them raw user passwords, run — in a secure, encrypted way — as far as you can." — Noted Security Teacher
Quick comparison (password grant vs other OAuth2 flows)
| Flow | Use case | Exposes user password to client? | Recommended for third-party apps? |
|---|---|---|---|
| Password Grant | First-party apps where backend and frontend are trusted | Yes | No |
| Authorization Code + PKCE | Mobile/web apps | No | Yes |
| Client Credentials | Machine-to-machine | N/A (no user) | N/A |
Closing — what you should remember (the recap rap)
- Password grant = app trades credentials for a token — convenient for first-party clients.
- Use OAuth2PasswordRequestForm for the token endpoint and OAuth2PasswordBearer to protect routes.
- Use DI to inject token validation and DB access cleanly; it's how your code stays readable and testable.
- Mind HTTPS, hash everything, and prefer safer flows for public clients.
Go forth and secure your endpoints! And when in doubt, ask: "Could this token compromise all the things if leaked?" If the answer is yes, make it shorter-lived or narrower-scoped.
version_name: "OAuth2 Password Flow — Slightly Chaotic, Highly Practical"
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!