Every route you built—FastAPI chat, SSE, WebSockets, job enqueue—is a paid LLM proxy until it is locked. Authentication answers who is calling; authorization answers what they may spend. Vendor keys (OPENAI_API_KEY) stay on the server. Users get your API keys or JWTs.
This is not OAuth theory for its own sake. It is how you stop a leaked SPA from draining the org wallet, and how Redis rate limits attach to identity. Next: deployment of secrets into real environments.
Learning Objectives
By the end of this lesson, students should be able to:
- Separate vendor secrets from user credentials (API keys / JWT / OIDC).
- Protect FastAPI routes with a dependency that returns 401/403.
- Issue hashed, prefix-visible product API keys for machine clients.
- Authenticate SSE and WebSocket upgrades, not only JSON POST.
- Attach quotas per principal via Redis.
- Avoid the “put sk- in localStorage” anti-pattern.
Authentication (authn) verifies identity. Authorization (authz) decides permissions (model SKU, token budget, admin). In this module, typical mechanisms are: Bearer JWT (human SPA after OIDC/login), product API keys (other backends), and optionally mTLS internally. OpenAI/Anthropic/Gemini keys are not user auth—they are upstream credentials your service uses after the caller is authenticated.
Credential Map
| Secret | Who holds it | Where it lives |
|---|---|---|
| Vendor LLM key | Your backend only | K8s Secret / vault / env |
| User JWT | Browser (memory / httpOnly cookie) | Issued by your IdP / auth route |
| Product API key | Partner server | Hashed in your DB; prefix shown once |
| Redis / DB passwords | Your backend | Same secret store as vendor keys |
FastAPI Bearer Dependency
Patterns vs Anti-Patterns
Do
- httpOnly Secure cookies or short JWTs
- Hash product keys at rest
- 401 missing/invalid; 403 authenticated but forbidden
Don’t
- Vendor
sk-in the SPA - API key in query logs forever
- Auth only POST, forget SSE/WS
Also
- CORS is not auth
- Ingress TLS is not auth
- Pydantic validation is not auth
JWT (users)
- Fits SPAs + OIDC
- Claims: sub, tenant, rpm
- Rotate signing keys
API keys (machines)
- Simple for partner backends
- Show secret once; store hash
- Revoke instantly in DB/Redis
Related Lectures
| Lecture | Role |
|---|---|
| API | 401/403 as part of the contract |
| Redis | Rate limits per sub |
| Streaming / WS | Same principal on upgrade/stream |
| Deployment | Where JWT_SECRET and vendor keys are injected |
“If the frontend is behind login, the API is safe.” Anyone can replay fetch to your origin. Second: hiding the OpenAI key in a “secret” Next.js env that is actually NEXT_PUBLIC_ still ships it. Third: 401 vs 403 matter for clients. Fourth: A1111 --gradio-auth is not a multi-tenant product IdP. Fifth: long-lived JWTs in localStorage are XSS loot.
Knowledge Check
- Short Answer: Authn vs authz in one line. Answer: Authn = who you are; authz = what you may do/spend.
- True/False:
OPENAI_API_KEYshould be sent by the browser. Answer: False. - Multiple Choice: Missing Bearer should be: (a) 401, (b) 204, (c) 301. Answer: (a).
- Short Answer: How should product API keys be stored? Answer: Hash (and maybe prefix) at rest; show the secret only once.
- True/False: CORS replaces authentication. Answer: False.
- Multiple Choice: Rate limits should key on: (a) authenticated principal, (b) only User-Agent, (c) CFG. Answer: (a).
- Short Answer: Why auth SSE/WS separately in your head? Answer: Easy to forget; EventSource/WS often cannot send headers the same way—design tokens carefully.
- True/False: 403 means “we know who you are but you may not.” Answer: True (typical usage).
- Multiple Choice: Next lecture: (a) Deployment, (b) UMAP, (c) Whisper. Answer: (a).
- Short Answer: Name one anti-pattern from this lecture. Answer: Any of: vendor key in SPA/localStorage, unauthenticated stream, query-string keys in logs, debug Flask auth.
Key Takeaways
- Vendor keys ≠ user auth; never ship
sk-to browsers. - JWT for humans, hashed API keys for machines; 401 vs 403.
- Protect POST, SSE, WS, and job enqueue alike.
- Quotas attach to
subvia Redis. - Next: Deployment.
Lab: Add the dependency to the FastAPI chat + stream routes. Demo 401 without token, 200 with JWT, 429 after Redis rpm. Attempt to paste OPENAI_API_KEY into the frontend and fail the review.
Whiteboard: Three boxes: User JWT, Product key, Vendor key. Only the last box arrows to OpenAI.
Recap: Authenticate callers; keep vendor keys server-side. Ship it next: Deployment.