Spaces:
Sleeping
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
Backend Changes:
- Modified
/api/v1/auth/loginto returnrefresh_tokenandexpires_in - Created new
/api/v1/auth/refresh-tokenendpoint with automatic token rotation - Added
refresh_session()method to SupabaseAuthService
- Modified
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
Implementation Guide:
- See:
docs/FRONTEND_TOKEN_REFRESH_GUIDE.md - Complete TypeScript/JavaScript code examples
- React and Vue integration examples
- Testing procedures
- See:
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:
- Go to Settings > Auth
- Find JWT Expiry
- Change from default (3600 seconds / 1 hour) to 86400 seconds (24 hours) or longer
- 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
- Implementation Guide:
docs/FRONTEND_TOKEN_REFRESH_GUIDE.md - Supabase Auth Docs: https://supabase.com/docs/guides/auth
- JWT Best Practices: https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/
β Recommended Solution
Use the automatic token refresh implementation (already complete):
- β Backend changes pushed to production
- π Follow frontend guide:
FRONTEND_TOKEN_REFRESH_GUIDE.md - π§ͺ Test the complete flow
- π Users stay logged in for 30 days seamlessly!
Step 3: Add refresh endpoint to backend
- Add
/api/v1/auth/refresh-tokenendpoint - 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
- Never store tokens in localStorage for sensitive apps - Use httpOnly cookies instead
- Always use HTTPS in production
- Rotate refresh tokens after each use (Supabase does this automatically)
- Set appropriate expiry times:
- Access token: 1-24 hours
- Refresh token: 7-30 days
- 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:
- Go to Supabase Dashboard
- Settings > Auth > JWT Expiry
- Change to 604800 seconds (7 days)
Long-term Solution:
- Implement token refresh endpoint in backend
- Add auto-refresh logic to frontend
- Handle 401 errors gracefully
- 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