JWT Authentication in Python: FastAPI and Flask Implementation Guide
Python's rich ecosystem provides multiple robust frameworks for implementing JWT authentication. In this comprehensive guide, we'll explore how to implement secure JWT authentication using two of Python's most popular web frameworks: FastAPI and Flask. We'll cover everything from basic setup to advanced security features, with a focus on real-world applications and best practices.
Why Choose Python for JWT Authentication?
Python offers several advantages for implementing JWT authentication:
- Rich Framework Ecosystem: Frameworks like FastAPI and Flask provide built-in support for JWT authentication.
- Strong Security Libraries: Python's security libraries are well-maintained and regularly updated.
- Type Hints Support: Modern Python's type hints help catch authentication-related bugs early.
- Excellent Documentation: Both FastAPI and Flask have comprehensive documentation for security features.
FastAPI Implementation
FastAPI is a modern, fast web framework that's perfect for building authenticated APIs. It provides built-in support for OAuth2 with JWT tokens and automatic API documentation.
Basic Setup
First, let's set up our FastAPI application with the necessary dependencies and models:
1from fastapi import FastAPI, Depends, HTTPException, status2from fastapi.security import OAuth2PasswordBearer3from jose import JWTError, jwt4from datetime import datetime, timedelta5from typing import Optional, Dict, Any6from pydantic import BaseModel78# Models9class Token(BaseModel):10 access_token: str11 token_type: str12 expires_in: int1314class TokenData(BaseModel):15 sub: Optional[str] = None16 exp: Optional[int] = None1718class User(BaseModel):19 username: str20 email: str21 disabled: Optional[bool] = None2223# Configuration24SECRET_KEY = "your-secret-key"25ALGORITHM = "HS256"26ACCESS_TOKEN_EXPIRE_MINUTES = 302728app = FastAPI()29oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
This setup provides several important features:
- Type-safe models using Pydantic
- Built-in OAuth2 password flow
- Automatic OpenAPI documentation
- Configurable token expiration
Token Generation
1def create_access_token(data: Dict[str, Any], expires_delta: timedelta) -> str:2 to_encode = data.copy()3 expire = datetime.utcnow() + expires_delta4 to_encode.update({5 "exp": expire,6 "iat": datetime.utcnow()7 })8 return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)910async def get_current_user(token: str = Depends(oauth2_scheme)) -> User:11 credentials_exception = HTTPException(12 status_code=status.HTTP_401_UNAUTHORIZED,13 detail="Could not validate credentials",14 headers={"WWW-Authenticate": "Bearer"},15 )16 try:17 payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])18 username: str = payload.get("sub")19 if username is None:20 raise credentials_exception21 token_data = TokenData(sub=username)22 except JWTError:23 raise credentials_exception2425 user = get_user(username=token_data.sub)26 if user is None:27 raise credentials_exception28 return user
Key security considerations in token handling:
- Use of cryptographically secure algorithms
- Proper error handling for invalid tokens
- Automatic token expiration
- Type-safe token validation
FastAPI Routes
1@app.post("/token", response_model=Token)2async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):3 user = authenticate_user(form_data.username, form_data.password)4 if not user:5 raise HTTPException(6 status_code=status.HTTP_401_UNAUTHORIZED,7 detail="Incorrect username or password",8 headers={"WWW-Authenticate": "Bearer"},9 )1011 access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)12 access_token = create_access_token(13 data={"sub": user.username},14 expires_delta=access_token_expires15 )1617 return {18 "access_token": access_token,19 "token_type": "bearer",20 "expires_in": ACCESS_TOKEN_EXPIRE_MINUTES * 6021 }2223@app.get("/users/me", response_model=User)24async def read_users_me(current_user: User = Depends(get_current_user)):25 return current_user
Flask Implementation
Basic Setup
1from flask import Flask, jsonify, request2from flask_jwt_extended import (3 JWTManager, create_access_token,4 jwt_required, get_jwt_identity5)6from datetime import timedelta78app = Flask(__name__)910# Setup Flask-JWT-Extended11app.config["JWT_SECRET_KEY"] = "your-secret-key"12app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)13jwt = JWTManager(app)
Token Management
1class TokenManager:2 def __init__(self):3 self.blacklist = set()45 def add_to_blacklist(self, jti):6 self.blacklist.add(jti)78 def is_blacklisted(self, jti):9 return jti in self.blacklist1011token_manager = TokenManager()1213@jwt.token_in_blocklist_loader14def check_if_token_revoked(jwt_header, jwt_payload):15 jti = jwt_payload["jti"]16 return token_manager.is_blacklisted(jti)
Flask Routes
1@app.route("/login", methods=["POST"])2def login():3 username = request.json.get("username", None)4 password = request.json.get("password", None)56 user = authenticate_user(username, password)7 if not user:8 return jsonify({"error": "Invalid credentials"}), 401910 access_token = create_access_token(11 identity=username,12 additional_claims={13 "roles": user.roles,14 "email": user.email15 }16 )1718 return jsonify({19 "access_token": access_token,20 "token_type": "bearer",21 "expires_in": 360022 })2324@app.route("/protected", methods=["GET"])25@jwt_required()26def protected():27 current_user = get_jwt_identity()28 return jsonify(logged_in_as=current_user), 200
Security Best Practices
Rate Limiting
1from flask_limiter import Limiter2from flask_limiter.util import get_remote_address34limiter = Limiter(5 app=app,6 key_func=get_remote_address,7 default_limits=["200 per day", "50 per hour"]8)910@app.route("/login", methods=["POST"])11@limiter.limit("5 per minute")12def login():13 # ... login logic14 pass
Refresh Token Implementation
1class RefreshTokenStore:2 def __init__(self):3 self.refresh_tokens = {}45 def store_refresh_token(self, user_id: str, refresh_token: str):6 self.refresh_tokens[user_id] = refresh_token78 def validate_refresh_token(self, user_id: str, refresh_token: str) -> bool:9 stored_token = self.refresh_tokens.get(user_id)10 return stored_token == refresh_token1112 def revoke_refresh_token(self, user_id: str):13 self.refresh_tokens.pop(user_id, None)1415@app.route("/refresh", methods=["POST"])16@jwt_required(refresh=True)17def refresh():18 identity = get_jwt_identity()19 access_token = create_access_token(identity=identity)20 return jsonify(access_token=access_token)
Error Handling
1class AuthError(Exception):2 def __init__(self, error: str, status_code: int):3 super().__init__()4 self.error = error5 self.status_code = status_code67@app.errorhandler(AuthError)8def handle_auth_error(error):9 return jsonify({10 "error": error.error,11 "message": "Authentication failed"12 }), error.status_code1314@jwt.expired_token_loader15def expired_token_callback(jwt_header, jwt_payload):16 return jsonify({17 "error": "token_expired",18 "message": "The token has expired"19 }), 401
Testing
1import pytest2from fastapi.testclient import TestClient34def test_login_success(client: TestClient):5 response = client.post(6 "/token",7 data={8 "username": "testuser",9 "password": "testpass"10 }11 )12 assert response.status_code == 20013 assert "access_token" in response.json()1415def test_protected_route(client: TestClient, valid_token):16 response = client.get(17 "/users/me",18 headers={"Authorization": f"Bearer {valid_token}"}19 )20 assert response.status_code == 200
Related Resources
- Try our JWT Token Validator
- Use our JWT Token Generator
- Learn JWT in Node.js
- Explore JSON Formatting
Conclusion
Python's FastAPI and Flask frameworks provide robust solutions for implementing JWT authentication. By following these patterns and security practices, you can build secure and scalable authentication systems.
Remember to check our other security guides and authentication tools for more resources!
1This guide provides comprehensive coverage of JWT implementation in Python, focusing on both FastAPI and Flask frameworks.