9.3 KiB
9.3 KiB
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
# 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
- Edit
JWT_ACCESS_TOKEN_EXPIRYfor default session length - Edit
JWT_ACCESS_TOKEN_EXPIRY_REMEMBER_MEfor remember me session length - Restart backend server
- No database migration needed (settings-only change)
Token Payload
Without Remember Me
{
"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
{
"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
-
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 -
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 -
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)
# 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:
- Verify
JWT_ACCESS_TOKEN_EXPIRYin settings.py - Check if remember_me is being passed correctly
- Decode token at jwt.io and check exp field
- Check browser clock sync (wrong time can cause early expiry)
Issue: Remember me checkbox doesn't work
Check:
- Browser dev tools → Network tab → login request
- Verify request payload includes
remember_me: true - Check response token and decode at jwt.io
- Verify
remember_me: truein token payload - Check backend logs for any errors
Issue: Token valid but still logged out
Check:
- Middleware might be denying request (account/plan validation)
- Check browser console for logout_reason
- Check backend logs for AUTO-LOGOUT messages
- Verify account status and plan status in database
Issue: Token refresh not working
Check:
- Verify refresh token exists in localStorage
- Check refresh token expiry (30 days)
- Check /api/v1/auth/refresh/ endpoint response
- Verify new access token being stored
- Check for CORS issues if using different domain
API Reference
POST /api/v1/auth/login/
Request:
{
"email": "user@example.com",
"password": "password123",
"remember_me": true // Optional, default: false
}
Response:
{
"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 settingsbackend/igny8_core/auth/utils.py- Token generation logicbackend/igny8_core/auth/serializers.py- LoginSerializer with remember_me fieldbackend/igny8_core/auth/urls.py- LoginView that handles remember_me
Frontend
frontend/src/store/authStore.ts- Login function with rememberMe parameterfrontend/src/components/auth/SignInForm.tsx- Checkbox that passes state to loginfrontend/src/services/api.ts- Token refresh logic
Documentation
AUTHENTICATION-HOLISTIC-REVAMP.md- Complete overview of authentication changesLOGOUT-CAUSES-COMPLETE-REFERENCE.md- All logout causes documentedLOGOUT-TRACKING-IMPLEMENTATION.md- Logout tracking system details
Future Enhancements
Potential Improvements
- Dynamic session cookie age: Make SESSION_COOKIE_AGE match access token expiry
- Device fingerprinting: "Remember this device" feature
- User preference: Save user's default remember me preference
- Admin control: Allow admins to set max remember me duration per account
- Activity-based expiry: Extend token on activity (sliding expiration)
- Sign out all devices: Invalidate all refresh tokens for a user