12 KiB
Example Pull Request Description
Summary
Add JWT-based authentication to replace session-based auth, enabling horizontal scaling and stateless API architecture.
Motivation
Current session-based authentication requires sticky sessions in the load balancer, preventing horizontal scaling and blocking mobile app launch. This migration enables:
- Dynamic server scaling during traffic spikes
- Mobile app support (no cookie requirement)
- Reduced infrastructure costs (~30%)
Changes
Added
- JWT token generation and validation service (
src/auth/jwt_service.py) - Authentication endpoints (
/auth/login,/auth/refresh,/auth/logout) - JWT validation middleware for API route protection
- Refresh token mechanism with 7-day expiry
- Rate limiting on authentication endpoints (10 req/min per IP)
- Token revocation support for logout
Changed
- API middleware now checks for JWT in
Authorization: Bearer <token>header - Error responses return 401 Unauthorized for invalid/expired tokens
- Configuration updated with JWT_SECRET and TOKEN_EXPIRY settings
Removed
Session management middleware(deprecated, will be removed in v3.0)
Test plan
Manual Testing
# 1. Install dependencies
pip install -r requirements.txt
# 2. Set JWT_SECRET environment variable
export JWT_SECRET="your-secret-key-here"
# 3. Start server
python run.py
# 4. Test login endpoint
curl -X POST http://localhost:5000/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"test123"}'
# Expected response:
# {
# "access_token": "eyJ...",
# "refresh_token": "eyJ...",
# "expires_in": 900
# }
# 5. Test protected endpoint with token
curl http://localhost:5000/api/users \
-H "Authorization: Bearer <access_token>"
# Expected: 200 OK with user list
# 6. Test with invalid token
curl http://localhost:5000/api/users \
-H "Authorization: Bearer invalid_token"
# Expected: 401 Unauthorized
# 7. Test refresh token
curl -X POST http://localhost:5000/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token":"<refresh_token>"}'
# Expected: New access token
Automated Tests
-
Unit tests:
tests/test_jwt_service.py(15 tests, all passing)- Token generation
- Token validation
- Token expiration
- Token revocation
-
Integration tests:
tests/integration/test_auth_endpoints.py(12 tests, all passing)- Login flow
- Token refresh flow
- Protected endpoint access
- Error handling
-
Coverage: 96% of auth module (78/81 lines)
Load Testing
# Test with 10,000 concurrent users
locust -f tests/load/test_auth.py --users 10000 --spawn-rate 100
Results:
- Average response time: 45ms
- 99th percentile: 120ms
- Error rate: 0%
- Memory usage: Stable at ~500MB (vs 2GB with sessions)
Edge Cases Tested
- Expired access token → Returns 401, client refreshes successfully
- Expired refresh token → Returns 401, client re-authenticates
- Malformed JWT → Returns 401 with clear error message
- Missing Authorization header → Returns 401
- Token revoked via logout → Subsequent requests fail with 401
- Concurrent requests with same token → All succeed (no race conditions)
Breaking Changes
Migration Required
Sessions will be deprecated in v3.0 (90 days from now). During the transition period, both session-based and JWT-based authentication are supported.
For API Clients
Before (session-based):
# Login
response = requests.post("/api/login", json={"username": "user", "password": "pass"})
session_cookie = response.cookies["session_id"]
# Authenticated request
requests.get("/api/users", cookies={"session_id": session_cookie})
After (JWT-based):
# Login
response = requests.post("/auth/login", json={"username": "user", "password": "pass"})
access_token = response.json()["access_token"]
refresh_token = response.json()["refresh_token"]
# Authenticated request
requests.get("/api/users", headers={"Authorization": f"Bearer {access_token}"})
# Refresh when access token expires
response = requests.post("/auth/refresh", json={"refresh_token": refresh_token})
new_access_token = response.json()["access_token"]
Migration Steps
- Update client code to use
/auth/loginendpoint - Store
access_tokenandrefresh_tokenfrom login response - Send
Authorization: Bearer <access_token>header with all API requests - Implement token refresh logic when access token expires (15 min)
- Handle 401 responses by refreshing or re-authenticating
Migration Guide
See full migration guide: docs/auth-migration-guide.md
Performance Impact
Improvements
- Memory usage: 75% reduction (2GB → 500MB for 10K users)
- Response time: 20% faster (session lookup eliminated)
- Scalability: Supports 5x more concurrent users (10K → 50K)
- Cost: ~30% infrastructure savings (no sticky sessions)
Regressions
None identified
Benchmarks
| Metric | Before (Sessions) | After (JWT) | Change |
|---|---|---|---|
| Avg response time | 55ms | 45ms | -18% ⬇️ |
| P99 response time | 180ms | 120ms | -33% ⬇️ |
| Memory (10K users) | 2GB | 500MB | -75% ⬇️ |
| Max concurrent users | 10,000 | 50,000 | +400% ⬆️ |
| Infrastructure cost | $5,000/mo | $3,500/mo | -30% ⬇️ |
Security Considerations
Security Measures Implemented
- RS256 algorithm: Asymmetric signing (public/private key pair)
- Short token expiry: Access tokens expire after 15 minutes
- Refresh token rotation: New refresh token issued on each refresh
- Rate limiting: 10 login attempts per minute per IP
- Secure storage: Refresh tokens HttpOnly, Secure flags
- No sensitive data: JWT payload contains only user_id, role
- Token revocation: Logout invalidates refresh tokens
Security Audit
- Penetration testing completed (no critical issues)
- Security review completed (approved by security team)
- OWASP Top 10 compliance verified
- Token storage best practices followed
Documentation Updates
- API documentation updated with JWT examples (docs/api/authentication.md)
- Migration guide published (docs/auth-migration-guide.md)
- Admin guide for token management added (docs/admin/token-management.md)
- Security best practices documented (docs/security/jwt-best-practices.md)
- CHANGELOG.md updated with breaking changes
Rollout Plan
Phase 1: Internal Beta (Week 1)
- Deploy to staging environment
- Dev team tests JWT authentication
- Monitor for issues
Phase 2: Gradual Rollout (Week 2-3)
- Enable JWT for 10% of users (feature flag)
- Monitor error rates, performance
- Increase to 50% if no issues
- Increase to 100% by end of Week 3
Phase 3: Deprecation Notice (Week 4)
- Display migration banner for session-based users
- Send email notification to API clients
- Update documentation with deprecation timeline
Phase 4: Session Removal (90 days)
- Remove session-based authentication code
- Release v3.0 with breaking changes
Checklist
- Tests added for new functionality
- All tests pass locally (
pytest tests/) - Integration tests pass (
pytest tests/integration/) - Load testing completed (10K concurrent users)
- Security audit completed
- API documentation updated
- Migration guide published
- CHANGELOG.md updated
- Breaking changes documented
- No new linter warnings
- Code coverage >95%
- Feature flag added for gradual rollout
- Rollback plan documented
Related
- Closes #123 (JWT authentication feature request)
- Blocks #125 (Mobile app launch)
- Related to #124 (Horizontal scaling infrastructure)
- Follow-up to #100 (Session management refactoring)
Screenshots / Diagrams
Authentication Flow
┌─────────┐ ┌─────────────┐
│ Client │ │ Auth API │
└─────────┘ └─────────────┘
│ │
│ 1. POST /auth/login │
│ { username, password } │
│──────────────────────────────────────────> │
│ │
│ │ 2. Validate credentials
│ │ 3. Generate tokens
│ │
│ 4. Return tokens │
│ { access_token, refresh_token } │
│ <──────────────────────────────────────── │
│ │
│ 5. Store tokens │
│ │
│ ┌───────────────┐
│ 6. API request + access_token │ API Server │
│──────────────────────────────────> └───────────────┘
│ │
│ │ 7. Validate JWT
│ │ 8. Process request
│ │
│ 9. Response │
│ <──────────────────────────────────────── │
Token Refresh Flow
┌─────────┐ ┌─────────────┐
│ Client │ │ Auth API │
└─────────┘ └─────────────┘
│ │
│ 1. API request with expired token │
│──────────────────────────────────────────> │
│ │
│ 2. 401 Unauthorized │
│ <──────────────────────────────────────── │
│ │
│ 3. POST /auth/refresh │
│ { refresh_token } │
│──────────────────────────────────────────> │
│ │
│ │ 4. Validate refresh_token
│ │ 5. Generate new access_token
│ │
│ 6. Return new access_token │
│ { access_token } │
│ <──────────────────────────────────────── │
│ │
│ 7. Retry API request with new token │
│──────────────────────────────────────────> │
│ │
│ 8. Success response │
│ <──────────────────────────────────────── │
Generated with Claude Code
Co-Authored-By: Claude noreply@anthropic.com