Spaces:
Sleeping
Sleeping
| # Quick Start: Refresh Token Rotation & Remember Me | |
| ## Setup | |
| ### 1. Update Environment Variables | |
| Add to your `.env` file: | |
| ```bash | |
| JWT_REMEMBER_ME_EXPIRE_DAYS=30 | |
| ``` | |
| ### 2. Create Database Indexes | |
| Run the index creation script: | |
| ```bash | |
| cd bookmyservice-ums | |
| python scripts/create_indexes.py | |
| ``` | |
| ### 3. Restart Your Service | |
| ```bash | |
| # If using uvicorn directly | |
| uvicorn app.app:app --reload | |
| # If using docker | |
| docker-compose restart ums | |
| ``` | |
| ## Testing | |
| ### Test 1: Login with Remember Me | |
| ```bash | |
| # Step 1: Send OTP | |
| curl -X POST http://localhost:8000/send-otp-login \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "login_input": "+1234567890" | |
| }' | |
| # Response includes temp_token | |
| # {"message": "OTP sent", "temp_token": "...", "user_exists": true} | |
| # Step 2: Login with OTP and remember_me | |
| curl -X POST http://localhost:8000/otp-login \ | |
| -H "Authorization: Bearer <temp_token>" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "login_input": "+1234567890", | |
| "otp": "777777", | |
| "remember_me": true, | |
| "device_info": "Chrome 120 on macOS" | |
| }' | |
| # Response includes access_token and refresh_token | |
| ``` | |
| ### Test 2: Refresh Token (with Rotation) | |
| ```bash | |
| curl -X POST http://localhost:8000/refresh-token \ | |
| -H "Authorization: Bearer <refresh_token>" | |
| # Response includes NEW access_token and NEW refresh_token | |
| # Old refresh_token is now invalid | |
| ``` | |
| ### Test 3: View Active Sessions | |
| ```bash | |
| curl -X GET http://localhost:8000/sessions \ | |
| -H "Authorization: Bearer <access_token>" | |
| # Response shows all active sessions with device info | |
| ``` | |
| ### Test 4: Logout from Specific Device | |
| ```bash | |
| curl -X DELETE http://localhost:8000/sessions/<token_id> \ | |
| -H "Authorization: Bearer <access_token>" | |
| ``` | |
| ### Test 5: Logout from All Devices | |
| ```bash | |
| curl -X POST http://localhost:8000/logout-all \ | |
| -H "Authorization: Bearer <access_token>" | |
| ``` | |
| ## Verify Token Rotation | |
| ### Check Token is Marked as Used | |
| ```python | |
| from app.models.refresh_token_model import RefreshTokenModel | |
| import asyncio | |
| async def check_token(): | |
| # After using a refresh token | |
| token_id = "your-token-id" | |
| metadata = await RefreshTokenModel.get_token_metadata(token_id) | |
| print(f"Used: {metadata.get('used')}") | |
| print(f"Used at: {metadata.get('used_at')}") | |
| asyncio.run(check_token()) | |
| ``` | |
| ### Test Replay Attack Detection | |
| ```bash | |
| # Use a refresh token | |
| curl -X POST http://localhost:8000/refresh-token \ | |
| -H "Authorization: Bearer <refresh_token>" | |
| # Try to use the SAME refresh token again | |
| curl -X POST http://localhost:8000/refresh-token \ | |
| -H "Authorization: Bearer <same_refresh_token>" | |
| # Should return 401 error and revoke entire token family | |
| ``` | |
| ## Maintenance | |
| ### Daily Cleanup (Cron Job) | |
| Add to your crontab: | |
| ```bash | |
| # Run cleanup daily at 2 AM | |
| 0 2 * * * cd /path/to/bookmyservice-ums && python scripts/cleanup_tokens.py >> /var/log/token_cleanup.log 2>&1 | |
| ``` | |
| Or use a scheduler in your application: | |
| ```python | |
| from apscheduler.schedulers.asyncio import AsyncIOScheduler | |
| from app.models.refresh_token_model import RefreshTokenModel | |
| scheduler = AsyncIOScheduler() | |
| @scheduler.scheduled_job('cron', hour=2) | |
| async def cleanup_job(): | |
| await RefreshTokenModel.cleanup_expired_tokens() | |
| scheduler.start() | |
| ``` | |
| ## Monitoring | |
| ### Check Token Statistics | |
| ```bash | |
| python scripts/cleanup_tokens.py | |
| ``` | |
| Output: | |
| ``` | |
| 📊 Token Statistics: | |
| Total tokens: 150 | |
| Active tokens: 120 | |
| Revoked tokens: 20 | |
| Expired tokens: 10 | |
| Used tokens: 80 | |
| Remember me tokens: 45 | |
| ``` | |
| ### Monitor Suspicious Activity | |
| The cleanup script automatically checks for: | |
| - Token families with >100 rotations | |
| - Unusual rotation patterns | |
| - Potential security breaches | |
| ## Frontend Integration Example | |
| ### React Hook for Token Management | |
| ```javascript | |
| import { useState, useEffect } from 'react'; | |
| export function useAuth() { | |
| const [accessToken, setAccessToken] = useState( | |
| localStorage.getItem('access_token') | |
| ); | |
| const [refreshToken, setRefreshToken] = useState( | |
| localStorage.getItem('refresh_token') | |
| ); | |
| // Auto-refresh before expiry | |
| useEffect(() => { | |
| const interval = setInterval(async () => { | |
| if (refreshToken) { | |
| try { | |
| const response = await fetch('/refresh-token', { | |
| method: 'POST', | |
| headers: { | |
| 'Authorization': `Bearer ${refreshToken}` | |
| } | |
| }); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| setAccessToken(data.access_token); | |
| setRefreshToken(data.refresh_token); | |
| localStorage.setItem('access_token', data.access_token); | |
| localStorage.setItem('refresh_token', data.refresh_token); | |
| } | |
| } catch (error) { | |
| console.error('Token refresh failed:', error); | |
| } | |
| } | |
| }, 7 * 60 * 1000); // Refresh every 7 minutes | |
| return () => clearInterval(interval); | |
| }, [refreshToken]); | |
| const login = async (loginInput, otp, rememberMe = false) => { | |
| const response = await fetch('/otp-login', { | |
| method: 'POST', | |
| headers: { | |
| 'Authorization': `Bearer ${tempToken}`, | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ | |
| login_input: loginInput, | |
| otp: otp, | |
| remember_me: rememberMe, | |
| device_info: navigator.userAgent | |
| }) | |
| }); | |
| const data = await response.json(); | |
| setAccessToken(data.access_token); | |
| setRefreshToken(data.refresh_token); | |
| localStorage.setItem('access_token', data.access_token); | |
| localStorage.setItem('refresh_token', data.refresh_token); | |
| }; | |
| const logout = async () => { | |
| await fetch('/logout', { | |
| method: 'POST', | |
| headers: { | |
| 'Authorization': `Bearer ${refreshToken}` | |
| } | |
| }); | |
| setAccessToken(null); | |
| setRefreshToken(null); | |
| localStorage.removeItem('access_token'); | |
| localStorage.removeItem('refresh_token'); | |
| }; | |
| return { accessToken, refreshToken, login, logout }; | |
| } | |
| ``` | |
| ## Troubleshooting | |
| ### Issue: "Invalid refresh token" after first use | |
| **Solution**: This is expected behavior with rotation. Always use the NEW refresh token from the response. | |
| ### Issue: All sessions revoked unexpectedly | |
| **Cause**: Token reuse detected (replay attack prevention) | |
| **Solution**: User must login again. Check for client-side bugs causing token reuse. | |
| ### Issue: Remember me not working | |
| **Check**: | |
| 1. `JWT_REMEMBER_ME_EXPIRE_DAYS` is set in .env | |
| 2. `remember_me: true` is sent in login request | |
| 3. Token metadata shows `remember_me: true` | |
| ### Issue: Sessions not showing up | |
| **Check**: | |
| 1. Database indexes are created | |
| 2. MongoDB connection is working | |
| 3. Token hasn't expired | |
| ## Security Checklist | |
| - [ ] HTTPS enabled in production | |
| - [ ] Refresh tokens stored securely (httpOnly cookies recommended) | |
| - [ ] Access tokens have short expiry (8 hours or less) | |
| - [ ] Remember me tokens have reasonable expiry (30 days max) | |
| - [ ] Cleanup script runs daily | |
| - [ ] Monitoring alerts set up for suspicious activity | |
| - [ ] Database indexes created | |
| - [ ] Rate limiting enabled on refresh endpoint | |
| ## Next Steps | |
| 1. Implement httpOnly cookies for refresh tokens (more secure than localStorage) | |
| 2. Add device fingerprinting for additional security | |
| 3. Set up monitoring and alerting | |
| 4. Implement push notifications for new logins | |
| 5. Add geolocation-based anomaly detection | |
| ## Support | |
| For issues or questions: | |
| 1. Check the logs: `tail -f app.log` | |
| 2. Run diagnostics: `python scripts/cleanup_tokens.py` | |
| 3. Review the full documentation: `REFRESH_TOKEN_ROTATION.md` | |