# 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:** - See: [`docs/FRONTEND_TOKEN_REFRESH_GUIDE.md`](./FRONTEND_TOKEN_REFRESH_GUIDE.md) - Complete TypeScript/JavaScript code examples - React and Vue integration examples - Testing procedures **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:** ```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: ```python # 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`](./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): 1. ✅ Backend changes pushed to production 2. 📝 Follow frontend guide: [`FRONTEND_TOKEN_REFRESH_GUIDE.md`](./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:** ```bash # 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:** ```javascript // 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`: ```bash # 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 - [Supabase Auth Documentation](https://supabase.com/docs/guides/auth) - [JWT Best Practices](https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/) - [Token Refresh Strategies](https://www.rfc-editor.org/rfc/rfc6749#section-6)