🔐 Flask API Authentication: Your Secret Handshake Guide
Imagine you have a super cool treehouse. You don’t want just anyone climbing up, right? You need a secret password! That’s exactly what API Authentication is—a way to make sure only the right people can use your app.
🎭 The Story: Building a Secret Club
Think of your Flask API as a Secret Club. To get in, members need:
- A membership card (Token)
- The card must be real (Validated)
- It must not be expired (Fresh)
Let’s learn how to create this amazing secret club system!
🎫 Token-Based Authentication
What Is It?
Imagine going to an amusement park. You pay once, get a wristband, and then show it at every ride. You don’t pay again—you just show your wristband!
Token-based authentication works the same way:
- You log in once (buy the ticket)
- You get a special code called a token (the wristband)
- For every request, you show your token (flash the wristband)
Why Use Tokens?
| Old Way (Sessions) | New Way (Tokens) |
|---|---|
| Server remembers you | Token remembers you |
| Needs storage | Stateless |
| Hard to scale | Easy to scale |
Simple Example
# When user logs in successfully
token = "abc123xyz789"
# User sends token with every request
headers = {
"Authorization": "Bearer abc123xyz789"
}
The word “Bearer” just means “I’m carrying this token!”
🎟️ JWT Basics
What Is JWT?
JWT stands for JSON Web Token. Think of it as a very special sealed envelope.
Inside the envelope:
- 📝 Header: Says “this is a letter” (type of token)
- 💌 Payload: The actual message (user info)
- 🔏 Signature: A wax seal proving it’s real
How JWT Looks
eyJhbGciOiJIUzI1NiJ9.
eyJ1c2VyIjoiam9obiJ9.
dBjftJeZ4CVP-mB92K27uhbUJU1p1
Three parts separated by dots (.):
- Header (encoded)
- Payload (encoded)
- Signature (secret sauce)
JWT Flow
graph TD A["User Logs In"] --> B["Server Creates JWT"] B --> C["Server Sends JWT to User"] C --> D["User Stores JWT"] D --> E["User Sends JWT with Requests"] E --> F["Server Verifies JWT"] F --> G["Access Granted!"]
🛠️ Flask-JWT-Extended
Your New Best Friend
Flask-JWT-Extended is like a helpful robot that handles all the JWT magic for you!
Installation
pip install flask-jwt-extended
Basic Setup
from flask import Flask
from flask_jwt_extended import JWTManager
app = Flask(__name__)
# Your super secret key (keep it safe!)
app.config["JWT_SECRET_KEY"] = "super-secret"
jwt = JWTManager(app)
Creating Tokens
from flask_jwt_extended import (
create_access_token
)
@app.route("/login", methods=["POST"])
def login():
username = request.json.get("username")
password = request.json.get("password")
# Check if credentials are correct
if username == "admin" and password == "pass":
# Create the magic token!
token = create_access_token(
identity=username
)
return {"token": token}
return {"error": "Bad credentials"}, 401
🔄 JWT Token Management
Access vs Refresh Tokens
Think of it like this:
- Access Token = Day pass (short-lived, 15 minutes)
- Refresh Token = Season pass (long-lived, 30 days)
When your day pass expires, use your season pass to get a new day pass!
Setting Up Both Tokens
from flask_jwt_extended import (
create_access_token,
create_refresh_token,
jwt_required,
get_jwt_identity
)
@app.route("/login", methods=["POST"])
def login():
username = request.json["username"]
# Create both tokens
access = create_access_token(
identity=username
)
refresh = create_refresh_token(
identity=username
)
return {
"access_token": access,
"refresh_token": refresh
}
Refresh Route
@app.route("/refresh", methods=["POST"])
@jwt_required(refresh=True)
def refresh():
# Get who the user is
current_user = get_jwt_identity()
# Give them a fresh access token
new_token = create_access_token(
identity=current_user
)
return {"access_token": new_token}
Token Expiry Configuration
from datetime import timedelta
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = (
timedelta(minutes=15)
)
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = (
timedelta(days=30)
)
🛡️ Protecting API Routes
The Magic Decorator
Use @jwt_required() to protect any route!
from flask_jwt_extended import jwt_required
@app.route("/secret")
@jwt_required()
def secret():
return {"message": "Welcome to the club!"}
Getting User Info
from flask_jwt_extended import (
jwt_required,
get_jwt_identity
)
@app.route("/profile")
@jwt_required()
def profile():
# Who is making this request?
current_user = get_jwt_identity()
return {"user": current_user}
Optional Protection
Sometimes you want to show different things to logged-in users:
@app.route("/hello")
@jwt_required(optional=True)
def hello():
current_user = get_jwt_identity()
if current_user:
return {"msg": f"Hello, {current_user}!"}
else:
return {"msg": "Hello, stranger!"}
Fresh Tokens for Sensitive Actions
For important actions (like changing password), require a “fresh” token:
@app.route("/change-password")
@jwt_required(fresh=True)
def change_password():
# Only works with freshly created tokens
return {"msg": "Password changed!"}
🌐 CORS Handling
The Problem: Different Domains
Imagine your frontend lives at myapp.com and your API lives at api.myapp.com. The browser says:
“Wait! These are different addresses! I won’t let them talk without permission!”
This is called CORS (Cross-Origin Resource Sharing).
What CORS Does
graph TD A["Frontend: myapp.com"] --> B{Browser Check} B -->|Different Origin| C["CORS Required"] C --> D["Server Says OK?"] D -->|Yes| E["Request Allowed"] D -->|No| F["Request Blocked!"]
How CORS Works
- Browser asks: “Can I access this API?”
- Server replies with headers
- If headers say “yes,” browser allows it
Manual CORS Headers
@app.after_request
def add_cors_headers(response):
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Headers"] = (
"Content-Type, Authorization"
)
response.headers["Access-Control-Allow-Methods"] = (
"GET, POST, PUT, DELETE"
)
return response
🎯 Flask-CORS Extension
The Easy Way!
Why write all that code when there’s a helper?
Installation
pip install flask-cors
Basic Usage
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
# Allow everyone (be careful in production!)
CORS(app)
Specific Origins
# Only allow your frontend
CORS(app, origins=["https://myapp.com"])
Route-Specific CORS
from flask_cors import cross_origin
@app.route("/public-api")
@cross_origin()
def public_api():
return {"msg": "Anyone can access!"}
@app.route("/private-api")
def private_api():
# No CORS here
return {"msg": "Only same-origin!"}
Full Configuration
CORS(app,
origins=["https://myapp.com"],
methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Content-Type", "Authorization"],
supports_credentials=True
)
🚦 API Rate Limiting
Why Limit Requests?
Imagine someone keeps pressing your doorbell 1000 times per second. Annoying, right? And it might break the doorbell!
Rate limiting says: “Hey, slow down! Only 100 rings per minute allowed!”
Flask-Limiter to the Rescue
pip install flask-limiter
Basic Setup
from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
app = Flask(__name__)
limiter = Limiter(
app=app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)
Route-Specific Limits
@app.route("/api/data")
@limiter.limit("10 per minute")
def get_data():
return {"data": "Here you go!"}
@app.route("/api/expensive")
@limiter.limit("1 per minute")
def expensive_operation():
return {"msg": "Heavy computation done!"}
Limit Formats
| Format | Meaning |
|---|---|
5 per minute |
5 requests every minute |
100 per hour |
100 requests per hour |
1000 per day |
1000 requests per day |
1/second |
1 request per second |
Exempt Certain Routes
@app.route("/health")
@limiter.exempt
def health_check():
# No limits here
return {"status": "healthy"}
Custom Error Message
@app.errorhandler(429)
def ratelimit_handler(e):
return {
"error": "Slow down! Too many requests.",
"retry_after": e.description
}, 429
🎬 Putting It All Together
Here’s a complete example combining everything:
from flask import Flask, request
from flask_jwt_extended import (
JWTManager,
create_access_token,
jwt_required,
get_jwt_identity
)
from flask_cors import CORS
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
app = Flask(__name__)
# Configurations
app.config["JWT_SECRET_KEY"] = "super-secret"
# Initialize extensions
jwt = JWTManager(app)
CORS(app, origins=["https://myapp.com"])
limiter = Limiter(
app=app,
key_func=get_remote_address,
default_limits=["100 per hour"]
)
# Login route
@app.route("/login", methods=["POST"])
@limiter.limit("5 per minute")
def login():
data = request.json
if data["user"] == "admin":
token = create_access_token(
identity=data["user"]
)
return {"token": token}
return {"error": "Invalid"}, 401
# Protected route
@app.route("/secret")
@jwt_required()
def secret():
user = get_jwt_identity()
return {"msg": f"Hello, {user}!"}
if __name__ == "__main__":
app.run()
🎉 You Did It!
You now know how to:
| Concept | You Learned |
|---|---|
| 🎫 Tokens | Digital wristbands for your API |
| 🎟️ JWT | The special sealed envelope |
| 🛠️ Flask-JWT | Your helpful JWT robot |
| 🔄 Token Management | Access + Refresh tokens |
| 🛡️ Route Protection | The @jwt_required() decorator |
| 🌐 CORS | Letting different domains talk |
| 🎯 Flask-CORS | Easy CORS with one line |
| 🚦 Rate Limiting | Stopping request spam |
Remember: Your API is like a secret club. With these tools, you control exactly who gets in and how often they can visit!
Now go build something amazing and keep it safe! 🚀
