🔐 FastAPI Authentication & Authorization
Your Secret Clubhouse: A Story About Keeping Things Safe
🏰 The Story Begins
Imagine you have a secret clubhouse. Not everyone can enter—only your best friends with the secret password. And even among your friends, some can only play games, while others can also unlock the treasure chest.
This is exactly how authentication (proving who you are) and authorization (what you’re allowed to do) work in FastAPI!
🎭 Meet Our Characters
| Character | Real World | FastAPI |
|---|---|---|
| 🚪 Gatekeeper | Checks your password | OAuth2 Password Flow |
| 🎫 Magic Ticket | Proves you entered | JWT Token |
| 🔒 Secret Code Maker | Scrambles passwords | Password Hashing |
| 👤 Name Tag | Shows who you are | Current User Pattern |
| 🎖️ Permission Badges | What you can do | OAuth2 Scopes |
🚪 Chapter 1: The Gatekeeper (OAuth2 Password Flow)
What Is It?
Think of a bouncer at a party. You tell them your name (username) and secret word (password). If they match the guest list, you get in!
How It Works
graph TD A[👤 User] -->|username + password| B[🚪 Login Endpoint] B -->|Check credentials| C{Valid?} C -->|Yes| D[🎫 Give Token] C -->|No| E[❌ Access Denied]
Simple Example
from fastapi import FastAPI, Depends
from fastapi.security import OAuth2PasswordBearer
from fastapi.security import OAuth2PasswordRequestForm
app = FastAPI()
# The gatekeeper setup
oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="login"
)
@app.post("/login")
def login(form: OAuth2PasswordRequestForm = Depends()):
# Check username and password
if form.username == "alex" and form.password == "secret123":
return {"access_token": "magic-ticket", "token_type": "bearer"}
return {"error": "Wrong credentials!"}
Why This Matters
- ✅ Standard way to exchange password for a ticket
- ✅ Works with any frontend app
- ✅ Secure and well-tested pattern
🎫 Chapter 2: The Magic Ticket (JWT Token Authentication)
What Is It?
After the gatekeeper lets you in, they give you a magic ticket (JWT). This ticket contains:
- Your name
- When it expires
- Special permissions
It’s like a wristband at a theme park—show it anywhere, and they know you belong!
Inside the Magic Ticket
graph TD A[🎫 JWT Token] --> B[Header: Type & Algorithm] A --> C[Payload: Your Info] A --> D[Signature: Proof It's Real]
Simple Example
from jose import jwt
from datetime import datetime, timedelta
SECRET_KEY = "super-secret-key"
ALGORITHM = "HS256"
def create_token(username: str):
# What goes in the ticket
data = {
"sub": username,
"exp": datetime.utcnow() + timedelta(hours=1)
}
# Create the magic ticket
token = jwt.encode(data, SECRET_KEY, algorithm=ALGORITHM)
return token
def verify_token(token: str):
# Read the magic ticket
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload["sub"] # Returns username
Why This Matters
- ✅ No need to check database every time
- ✅ Ticket carries all needed info
- ✅ Can’t be faked (signature proves it’s real)
🔒 Chapter 3: The Secret Code Maker (Password Hashing)
What Is It?
Imagine writing your password on paper, then putting it through a magic shredder that turns it into confetti. Nobody can read the original, but the same password always makes the same confetti pattern!
This is hashing—we never store actual passwords, only their scrambled versions.
Why Not Store Real Passwords?
| Bad Idea 😱 | Good Idea 🎉 |
|---|---|
Password: ilovecats |
Hash: $2b$12$X5kQ... |
| If stolen, hacker knows password | If stolen, hacker sees gibberish |
Simple Example
from passlib.context import CryptContext
# Our magic shredder
pwd_context = CryptContext(
schemes=["bcrypt"],
deprecated="auto"
)
def hash_password(password: str):
# Turn password into confetti
return pwd_context.hash(password)
def verify_password(plain: str, hashed: str):
# Check if confetti patterns match
return pwd_context.verify(plain, hashed)
# Usage
hashed = hash_password("ilovecats")
# Result: $2b$12$X5kQ...
is_correct = verify_password("ilovecats", hashed)
# Result: True
Why This Matters
- ✅ Even if database is stolen, passwords are safe
- ✅ Same password = same hash (for verification)
- ✅ Can’t reverse the hash to get password
👤 Chapter 4: The Name Tag (Current User Pattern)
What Is It?
Once you’re inside the clubhouse with your magic ticket, we need a way to always know who you are. It’s like wearing a name tag that everyone can read!
In FastAPI, we use dependencies to automatically figure out who’s making each request.
How It Works
graph TD A[📨 Request with Token] --> B[🔍 Extract Token] B --> C[🎫 Decode JWT] C --> D[🗄️ Find User in Database] D --> E[👤 Return User Object]
Simple Example
from fastapi import Depends, HTTPException
def get_current_user(token: str = Depends(oauth2_scheme)):
# Decode the magic ticket
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username = payload.get("sub")
except:
raise HTTPException(status_code=401, detail="Invalid token")
# Find user in database
user = find_user(username)
if not user:
raise HTTPException(status_code=401, detail="User not found")
return user
# Use in any endpoint
@app.get("/my-profile")
def read_profile(user = Depends(get_current_user)):
return {"username": user.username, "email": user.email}
Why This Matters
- ✅ Any endpoint can know who’s asking
- ✅ Reusable—write once, use everywhere
- ✅ Automatic—FastAPI handles the magic
🎖️ Chapter 5: Permission Badges (OAuth2 Scopes)
What Is It?
In our clubhouse, different friends have different badges:
- 🟢 Reader badge: Can read the comic books
- 🔵 Writer badge: Can add new comics
- 🔴 Admin badge: Can do everything!
Scopes are like these badges—they control what each user can do.
Common Scope Examples
| Scope | Permission |
|---|---|
read:items |
View items |
write:items |
Create/edit items |
delete:items |
Remove items |
admin |
Full access |
Simple Example
from fastapi.security import SecurityScopes, OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="login",
scopes={
"read:items": "Read items",
"write:items": "Create items",
"admin": "Full access"
}
)
def get_current_user(
security_scopes: SecurityScopes,
token: str = Depends(oauth2_scheme)
):
# Decode token and get user's scopes
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
token_scopes = payload.get("scopes", [])
# Check if user has required scopes
for scope in security_scopes.scopes:
if scope not in token_scopes:
raise HTTPException(
status_code=403,
detail=f"Missing permission: {scope}"
)
return find_user(payload["sub"])
# Endpoint requiring specific scope
@app.delete("/items/{id}")
def delete_item(
id: int,
user = Security(get_current_user, scopes=["delete:items"])
):
return {"deleted": id}
Why This Matters
- ✅ Fine-grained control over who does what
- ✅ One user can have multiple permissions
- ✅ Easy to add new permission types
🎯 Putting It All Together
Here’s the complete flow of a user logging in and accessing protected content:
graph TD A[👤 User types password] --> B[🔒 Hash and compare] B --> C{Match?} C -->|Yes| D[🎫 Create JWT with scopes] D --> E[📱 User stores token] E --> F[📨 User makes request] F --> G[🔍 Extract token] G --> H[👤 Get current user] H --> I[🎖️ Check scopes] I -->|Has permission| J[✅ Return data] I -->|No permission| K[❌ 403 Forbidden] C -->|No| L[❌ 401 Unauthorized]
🌟 Quick Recap
| Concept | One-Line Summary |
|---|---|
| OAuth2 Password Flow | Exchange username+password for a token |
| JWT Token | A signed ticket proving who you are |
| Password Hashing | Scramble passwords so they can’t be stolen |
| Current User Pattern | Automatically know who’s making requests |
| OAuth2 Scopes | Control what each user can do |
💡 Remember This!
Authentication = “Who are you?” (Show your ID)
Authorization = “What can you do?” (Check your badges)
Just like your secret clubhouse:
- 🚪 Prove you know the password
- 🎫 Get your magic ticket
- 🔒 Never write passwords in plain text
- 👤 Wear your name tag everywhere
- 🎖️ Show your badges to unlock special areas
🚀 You Did It!
You now understand how FastAPI keeps apps secure! These five concepts work together like a well-oiled machine to protect your users and their data.
Next step: Try building your own login system using these patterns. Start simple—just the password flow and JWT—then add scopes when you’re ready!
Remember: Security isn’t scary. It’s just being a good gatekeeper for your clubhouse! 🏰