bookmyservice-ums / QUICK_START_ROTATION.md
MukeshKapoor25's picture
remember me
9b51d59

Quick Start: Refresh Token Rotation & Remember Me

Setup

1. Update Environment Variables

Add to your .env file:

JWT_REMEMBER_ME_EXPIRE_DAYS=30

2. Create Database Indexes

Run the index creation script:

cd bookmyservice-ums
python scripts/create_indexes.py

3. Restart Your Service

# If using uvicorn directly
uvicorn app.app:app --reload

# If using docker
docker-compose restart ums

Testing

Test 1: Login with Remember Me

# 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)

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

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

curl -X DELETE http://localhost:8000/sessions/<token_id> \
  -H "Authorization: Bearer <access_token>"

Test 5: Logout from All Devices

curl -X POST http://localhost:8000/logout-all \
  -H "Authorization: Bearer <access_token>"

Verify Token Rotation

Check Token is Marked as Used

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

# 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:

# 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:

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

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

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