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:**
- 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)