Files
igny8/REMEMBER-ME-FEATURE-REFERENCE.md
IGNY8 VPS (Salman) 5366cc1805 logo out issues fixes
2025-12-15 16:08:47 +00:00

311 lines
9.3 KiB
Markdown

# Remember Me Feature - Quick Reference
## Overview
The "Keep me logged in" checkbox on the login page now controls JWT token expiry time:
- **Unchecked (default):** 1 hour session
- **Checked:** 20 days session
## How It Works
### Frontend Flow
```
User checks "Keep me logged in" checkbox
SignInForm.tsx passes isChecked to login()
authStore.login(email, password, rememberMe)
POST /api/v1/auth/login/ with { email, password, remember_me: true }
```
### Backend Flow
```
LoginSerializer validates remember_me field (default: false)
LoginView extracts remember_me from validated_data
generate_access_token(user, account, remember_me=True)
get_access_token_expiry(remember_me=True) returns 20 days
JWT token created with 20-day expiry
Token sent to frontend in response
```
## Configuration
### Token Expiry Settings
**File:** `backend/igny8_core/settings.py`
```python
# Default expiry (remember me unchecked)
JWT_ACCESS_TOKEN_EXPIRY = timedelta(hours=1)
# Extended expiry (remember me checked)
JWT_ACCESS_TOKEN_EXPIRY_REMEMBER_ME = timedelta(days=20)
# Refresh token expiry (independent of remember me)
JWT_REFRESH_TOKEN_EXPIRY = timedelta(days=30)
```
### To Change Expiry Times
1. Edit `JWT_ACCESS_TOKEN_EXPIRY` for default session length
2. Edit `JWT_ACCESS_TOKEN_EXPIRY_REMEMBER_ME` for remember me session length
3. Restart backend server
4. No database migration needed (settings-only change)
## Token Payload
### Without Remember Me
```json
{
"user_id": 123,
"account_id": 456,
"email": "user@example.com",
"exp": 1704124800, // 1 hour from now
"iat": 1704121200,
"type": "access",
"remember_me": false
}
```
### With Remember Me
```json
{
"user_id": 123,
"account_id": 456,
"email": "user@example.com",
"exp": 1705846800, // 20 days from now
"iat": 1704121200,
"type": "access",
"remember_me": true
}
```
## User Experience
### Scenario 1: Quick Session (Unchecked)
- User logs in without checking "Keep me logged in"
- User works for 1 hour
- After 1 hour, token expires
- Next API call returns 401
- Frontend attempts token refresh
- If refresh succeeds, user stays logged in
- If refresh fails (after 30 days), user sees login page
### Scenario 2: Extended Session (Checked)
- User logs in with "Keep me logged in" checked
- User works for 20 days
- Token remains valid for 20 days
- After 20 days, token expires
- Next API call returns 401
- Frontend attempts token refresh
- If refresh succeeds, user stays logged in
- If refresh fails (after 30 days), user sees login page
### Scenario 3: Mixed Usage
- User logs in with remember me on Device A (20 days)
- User logs in without remember me on Device B (1 hour)
- Each device has independent tokens with different expiry
- Device A stays logged in for 20 days
- Device B expires after 1 hour (but can refresh for up to 30 days)
## Security Considerations
### Why 1 Hour Default?
- Balance between UX and security
- Short enough to limit exposure if token stolen
- Long enough to avoid constant re-authentication
- Refresh token (30 days) extends session without requiring re-login
### Why 20 Days for Remember Me?
- User explicitly opted in for extended session
- Still expires eventually (not permanent)
- Refresh token expiry (30 days) provides hard limit
- Common pattern for "remember me" features
### Token Storage
- Access token stored in localStorage (XSS risk mitigation via CSP)
- Refresh token stored in localStorage
- HttpOnly session cookie used for fallback authentication
- All sensitive API calls require valid access token
## Testing
### Manual Test Steps
1. **Test Default (1 hour):**
```
1. Login WITHOUT checking "Keep me logged in"
2. Open browser dev tools → Application → Local Storage
3. Find 'auth-storage' key
4. Copy token value
5. Go to jwt.io and decode
6. Verify exp is ~1 hour from now
7. Verify remember_me: false
```
2. **Test Remember Me (20 days):**
```
1. Login WITH "Keep me logged in" checked
2. Open browser dev tools → Application → Local Storage
3. Find 'auth-storage' key
4. Copy token value
5. Go to jwt.io and decode
6. Verify exp is ~20 days from now
7. Verify remember_me: true
```
3. **Test Expiry Behavior:**
```
1. Login with remember me
2. Wait for access token to expire (or manually change exp in localStorage)
3. Make API call
4. Verify 401 received
5. Verify token refresh attempted
6. Verify new token received (if refresh token valid)
7. Verify original request retried successfully
```
### Automated Test (Backend)
```python
# backend/igny8_core/auth/tests/test_remember_me.py
from django.test import TestCase
from igny8_core.auth.utils import generate_access_token, decode_token
from igny8_core.auth.models import User, Account
from datetime import timedelta
from django.utils import timezone
class RememberMeTestCase(TestCase):
def setUp(self):
self.account = Account.objects.create(name="Test Account")
self.user = User.objects.create_user(
email="test@example.com",
password="testpass",
account=self.account
)
def test_default_expiry(self):
"""Test that default token expires in 1 hour"""
token = generate_access_token(self.user, self.account, remember_me=False)
payload = decode_token(token)
exp_time = timezone.datetime.fromtimestamp(payload['exp'], tz=timezone.utc)
now = timezone.now()
expiry_delta = exp_time - now
# Should be ~1 hour (allow 5 minute variance)
self.assertLess(expiry_delta, timedelta(hours=1, minutes=5))
self.assertGreater(expiry_delta, timedelta(minutes=55))
self.assertEqual(payload['remember_me'], False)
def test_remember_me_expiry(self):
"""Test that remember me token expires in 20 days"""
token = generate_access_token(self.user, self.account, remember_me=True)
payload = decode_token(token)
exp_time = timezone.datetime.fromtimestamp(payload['exp'], tz=timezone.utc)
now = timezone.now()
expiry_delta = exp_time - now
# Should be ~20 days (allow 1 hour variance)
self.assertLess(expiry_delta, timedelta(days=20, hours=1))
self.assertGreater(expiry_delta, timedelta(days=19, hours=23))
self.assertEqual(payload['remember_me'], True)
```
## Troubleshooting
### Issue: Token expires too quickly
**Check:**
1. Verify `JWT_ACCESS_TOKEN_EXPIRY` in settings.py
2. Check if remember_me is being passed correctly
3. Decode token at jwt.io and check exp field
4. Check browser clock sync (wrong time can cause early expiry)
### Issue: Remember me checkbox doesn't work
**Check:**
1. Browser dev tools → Network tab → login request
2. Verify request payload includes `remember_me: true`
3. Check response token and decode at jwt.io
4. Verify `remember_me: true` in token payload
5. Check backend logs for any errors
### Issue: Token valid but still logged out
**Check:**
1. Middleware might be denying request (account/plan validation)
2. Check browser console for logout_reason
3. Check backend logs for AUTO-LOGOUT messages
4. Verify account status and plan status in database
### Issue: Token refresh not working
**Check:**
1. Verify refresh token exists in localStorage
2. Check refresh token expiry (30 days)
3. Check /api/v1/auth/refresh/ endpoint response
4. Verify new access token being stored
5. Check for CORS issues if using different domain
## API Reference
### POST /api/v1/auth/login/
**Request:**
```json
{
"email": "user@example.com",
"password": "password123",
"remember_me": true // Optional, default: false
}
```
**Response:**
```json
{
"success": true,
"data": {
"user": {
"id": 123,
"email": "user@example.com",
"username": "user",
"role": "owner",
"account": { ... },
...
},
"tokens": {
"access": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"access_expires_at": "2024-01-22T12:00:00Z",
"refresh_expires_at": "2024-02-20T12:00:00Z"
}
}
}
```
## Related Files
### Backend
- `backend/igny8_core/settings.py` - Token expiry settings
- `backend/igny8_core/auth/utils.py` - Token generation logic
- `backend/igny8_core/auth/serializers.py` - LoginSerializer with remember_me field
- `backend/igny8_core/auth/urls.py` - LoginView that handles remember_me
### Frontend
- `frontend/src/store/authStore.ts` - Login function with rememberMe parameter
- `frontend/src/components/auth/SignInForm.tsx` - Checkbox that passes state to login
- `frontend/src/services/api.ts` - Token refresh logic
### Documentation
- `AUTHENTICATION-HOLISTIC-REVAMP.md` - Complete overview of authentication changes
- `LOGOUT-CAUSES-COMPLETE-REFERENCE.md` - All logout causes documented
- `LOGOUT-TRACKING-IMPLEMENTATION.md` - Logout tracking system details
## Future Enhancements
### Potential Improvements
1. **Dynamic session cookie age:** Make SESSION_COOKIE_AGE match access token expiry
2. **Device fingerprinting:** "Remember this device" feature
3. **User preference:** Save user's default remember me preference
4. **Admin control:** Allow admins to set max remember me duration per account
5. **Activity-based expiry:** Extend token on activity (sliding expiration)
6. **Sign out all devices:** Invalidate all refresh tokens for a user