JWT Auth Flow (Login → Cookie → Protected Route)
The complete auth loop: register, login (get JWT in cookie), access a protected route, logout. The backbone of OpenBenchML.
🚀 Mini Project · 2 filesProject files
from fastapi import FastAPI, HTTPException, Request, Response, Depends
from fastapi.responses import HTMLResponse
from pydantic import BaseModel
from jose import jwt, JWTError
from datetime import datetime, timedelta, timezone
import uvicorn, secrets
app = FastAPI()
SECRET = secrets.token_hex(32) # in prod, load from env var
ALGO = 'HS256'
# Fake user DB — use a real DB in production
USERS = {}
class LoginIn(BaseModel):
username: str
password: str
def make_token(user_id: str) -> str:
payload = {
'sub': user_id,
'exp': datetime.now(timezone.utc) + timedelta(hours=1),
}
return jwt.encode(payload, SECRET, algorithm=ALGO)
def current_user(request: Request) -> str:
token = request.cookies.get('token')
if not token:
raise HTTPException(401, 'not logged in')
try:
payload = jwt.decode(token, SECRET, algorithms=[ALGO])
return payload['sub']
except JWTError:
raise HTTPException(401, 'bad token')
@app.post('/register')
async def register(body: LoginIn):
if body.username in USERS:
raise HTTPException(400, 'exists')
USERS[body.username] = body.password # hash in prod!
return {'status': 'registered'}
@app.post('/login')
async def login(body: LoginIn, response: Response):
if USERS.get(body.username) != body.password:
raise HTTPException(401, 'bad credentials')
token = make_token(body.username)
response.set_cookie(
'token', token,
httponly=True, # JS can't read it — XSS-safe
samesite='lax', # CSRF defence
max_age=3600,
)
return {'status': 'logged in'}
@app.get('/me')
async def me(user: str = Depends(current_user)):
return {'user': user, 'secret_data': 'only logged-in users see this'}
@app.post('/logout')
async def logout(response: Response):
response.delete_cookie('token')
return {'status': 'logged out'}
if __name__ == '__main__':
uvicorn.run(app, host='0.0.0.0', port=8000)
# 1. Register
curl -X POST http://localhost:8000/register \
-H 'Content-Type: application/json' \
-d '{"username":"ada","password":"secret"}'
# 2. Login — saves JWT in a cookie jar
curl -c cookies.txt -X POST http://localhost:8000/login \
-H 'Content-Type: application/json' \
-d '{"username":"ada","password":"secret"}'
# 3. Access protected route — sends the cookie
curl -b cookies.txt http://localhost:8000/me
# {"user":"ada","secret_data":"only logged-in users see this"}
# 4. Without the cookie — 401
curl http://localhost:8000/me
# {"detail":"not logged in"}
# 5. Logout
curl -b cookies.txt -c cookies.txt -X POST http://localhost:8000/logout
How it works
JWT auth has 4 steps: (1) REGISTER — user picks a username + password, server stores them (hashed in production — never store plaintext!). (2) LOGIN — user submits credentials, server verifies, then creates a JWT containing the user id + expiry, signs it with SECRET, and sets it as an httpOnly cookie. httpOnly means JavaScript cannot read the cookie — defeats XSS token theft. (3) PROTECTED ROUTE — every request automatically sends the cookie. The current_user dependency decodes the JWT, verifies the signature (if SECRET is wrong, decode fails), checks expiry, and returns the user id. If anything fails, 401. (4) LOGOUT — delete the cookie. The key insight: the server is stateless — it doesn't remember who's logged in. The JWT in the cookie IS the proof. Anyone with the SECRET can forge tokens, so SECRET must be long, random, and never committed to git.
⚡ Open in Notebook