swiftops-backend / docs /dev /auth /JWT_TOKEN_EXPIRATION_FIX.md
kamau1's picture
chore: migrate to useast organize the docs, delete redundant migrations
c4f7e3e

JWT Token Expiration Fix

Problem

Users are getting logged out too quickly. Token expired after ~9 hours instead of the expected 24 hours.

Error from logs:

ERROR: 2025-11-18T05:38:46 - app.core.supabase_auth: Get user error: invalid JWT: unable to parse or verify signature, token has invalid claims: token is expired

Root Cause

The JWT tokens are managed by Supabase Auth, not the backend ACCESS_TOKEN_EXPIRE_MINUTES setting. Supabase has its own JWT expiry configuration that overrides your backend setting.


βœ… WORKING SOLUTION: Automatic Token Refresh

Status: βœ… Implemented and tested
Requirements: Supabase Free tier (no upgrade needed)
User Experience: Users stay logged in for 30 days

What Was Implemented

  1. Backend Changes:

    • Modified /api/v1/auth/login to return refresh_token and expires_in
    • Created new /api/v1/auth/refresh-token endpoint with automatic token rotation
    • Added refresh_session() method to SupabaseAuthService
  2. How It Works:

    • User logs in β†’ receives access token (1 hour) + refresh token (30 days)
    • Frontend schedules auto-refresh 5 minutes before expiration
    • Token refreshes automatically in background
    • Users stay logged in seamlessly for up to 30 days
  3. Implementation Guide:

Why This Solution:

  • βœ… Works with Supabase Free tier
  • βœ… No Pro plan upgrade required
  • βœ… Better security (token rotation)
  • βœ… Seamless user experience
  • βœ… Industry best practice

❌ Option 1: Configure Supabase JWT Expiry

Status: ❌ Not available on Supabase Free tier
Requirements: Supabase Pro plan ($25/month)

⚠️ This option requires upgrading to Supabase Pro plan

In your Supabase Dashboard:

  1. Go to Settings > Auth
  2. Find JWT Expiry
  3. Change from default (3600 seconds / 1 hour) to 86400 seconds (24 hours) or longer
  4. Save changes

Or via Supabase SQL:

-- Update JWT expiry to 7 days (604800 seconds)
ALTER DATABASE postgres
SET app.settings.jwt_exp = '604800';

πŸ“‹ Alternative Options (Reference Only)

Option 2: Backend Token Configuration

Status: ⚠️ Limited effect (Supabase controls JWT expiry)

File: src/app/config_settings.py

You can extend the backend token setting, but this won't affect Supabase-managed JWTs:

# Change from:
ACCESS_TOKEN_EXPIRE_MINUTES: int = 1440  # 24 hours

# To:
ACCESS_TOKEN_EXPIRE_MINUTES: int = 10080  # 7 days
# Or:
ACCESS_TOKEN_EXPIRE_MINUTES: int = 43200  # 30 days

Note: This only affects custom JWT tokens if you create them outside Supabase. Supabase tokens are controlled by Supabase Auth settings.


πŸ“š Additional Resources


βœ… Recommended Solution

Use the automatic token refresh implementation (already complete):

  1. βœ… Backend changes pushed to production
  2. πŸ“ Follow frontend guide: FRONTEND_TOKEN_REFRESH_GUIDE.md
  3. πŸ§ͺ Test the complete flow
  4. πŸŽ‰ Users stay logged in for 30 days seamlessly!

Step 3: Add refresh endpoint to backend

  • Add /api/v1/auth/refresh-token endpoint
  • Implement refresh_session() in SupabaseAuthService

Testing

Test token expiration:

# Login
curl -X POST http://localhost:8000/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@swiftops.com","password":"yourpassword"}'

# Get access_token and refresh_token from response

# Wait for token to expire (or manually expire it)

# Test refresh
curl -X POST http://localhost:8000/api/v1/auth/refresh-token \
  -H "Content-Type: application/json" \
  -d '{"refresh_token":"YOUR_REFRESH_TOKEN"}'

Frontend test:

// Force token refresh
await refreshAccessToken();

// Check if it works
const response = await api.get('/api/v1/auth/me');
console.log(response.data); // Should return user data

Security Considerations

  1. Never store tokens in localStorage for sensitive apps - Use httpOnly cookies instead
  2. Always use HTTPS in production
  3. Rotate refresh tokens after each use (Supabase does this automatically)
  4. Set appropriate expiry times:
    • Access token: 1-24 hours
    • Refresh token: 7-30 days
  5. Implement token revocation for logout

Environment Variables

Add to .env:

# JWT Settings
ACCESS_TOKEN_EXPIRE_MINUTES=10080  # 7 days
REFRESH_TOKEN_EXPIRE_DAYS=30

# Supabase JWT Expiry (configure in Supabase Dashboard)
# JWT_EXP=604800  # 7 days

Summary

Immediate Fix:

  1. Go to Supabase Dashboard
  2. Settings > Auth > JWT Expiry
  3. Change to 604800 seconds (7 days)

Long-term Solution:

  1. Implement token refresh endpoint in backend
  2. Add auto-refresh logic to frontend
  3. Handle 401 errors gracefully
  4. Test thoroughly

Result:

  • Users stay logged in for 7 days
  • Tokens refresh automatically before expiration
  • Smooth user experience without unexpected logouts
  • Proper security with shorter-lived access tokens

Additional Resources