Files
igny8/docs/logout-issues/AUTHENTICATION-HOLISTIC-REVAMP.md
IGNY8 VPS (Salman) 69c0fd8b69 reorg
2025-12-17 00:27:53 +00:00

11 KiB

Authentication System Holistic Revamp - Complete

Overview

This document summarizes the comprehensive authentication system overhaul completed to fix random logout issues and improve user experience.

Problem Statement

Users were experiencing frequent, random logouts due to:

  1. Overly aggressive session validation - Middleware checking session contamination on every request
  2. Short token expiry - 15-minute JWT access tokens causing constant re-authentication
  3. False-positive logout triggers - Session ID mismatches causing immediate logouts
  4. No user control - No remember me option for extended sessions

Changes Made

1. Backend Token Expiry Settings

File: backend/igny8_core/settings.py

# BEFORE:
JWT_ACCESS_TOKEN_EXPIRY = timedelta(minutes=15)  # Too short!
SESSION_COOKIE_AGE = 86400  # 24 hours

# AFTER:
JWT_ACCESS_TOKEN_EXPIRY = timedelta(hours=1)  # Default: 1 hour
JWT_ACCESS_TOKEN_EXPIRY_REMEMBER_ME = timedelta(days=20)  # Remember me: 20 days
SESSION_COOKIE_AGE = 3600  # 1 hour (aligned with JWT)

Impact: Users now have 1 hour before needing to re-authenticate (or 20 days if remember me is checked).


2. Remember Me Functionality

Files:

  • backend/igny8_core/auth/utils.py
  • backend/igny8_core/auth/serializers.py
  • backend/igny8_core/auth/urls.py

Backend Changes:

# utils.py - Added remember_me support
def get_access_token_expiry(remember_me=False):
    if remember_me:
        return timezone.now() + settings.JWT_ACCESS_TOKEN_EXPIRY_REMEMBER_ME
    return timezone.now() + settings.JWT_ACCESS_TOKEN_EXPIRY

def generate_access_token(user, account=None, remember_me=False):
    expiry = get_access_token_expiry(remember_me=remember_me)
    payload = {
        'user_id': user.id,
        'account_id': account.id if account else None,
        'email': user.email,
        'exp': int(expiry.timestamp()),
        'iat': int(now.timestamp()),
        'type': 'access',
        'remember_me': remember_me,  # NEW: Track remember me in token
    }
    # ... rest of function

# serializers.py - Added field
class LoginSerializer(serializers.Serializer):
    email = serializers.EmailField()
    password = serializers.CharField(write_only=True)
    remember_me = serializers.BooleanField(required=False, default=False)  # NEW

# urls.py - LoginView updated
def post(self, request):
    serializer = LoginSerializer(data=request.data)
    if serializer.is_valid():
        email = serializer.validated_data['email']
        password = serializer.validated_data['password']
        remember_me = serializer.validated_data.get('remember_me', False)  # NEW
        # ...
        access_token = generate_access_token(user, account, remember_me=remember_me)  # NEW

Impact: Backend now respects remember me checkbox and generates tokens with appropriate expiry.


3. Frontend Remember Me Integration

Files:

  • frontend/src/store/authStore.ts
  • frontend/src/components/auth/SignInForm.tsx

Frontend Changes:

// authStore.ts - Updated login function
login: async (email, password, rememberMe = false) => {  // NEW parameter
    set({ loading: true });
    try {
        const API_BASE_URL = import.meta.env.VITE_BACKEND_URL || 'https://api.igny8.com/api';
        const response = await fetch(`${API_BASE_URL}/v1/auth/login/`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ 
                email, 
                password, 
                remember_me: rememberMe  // NEW: Send to backend
            }),
        });
        // ... rest of function

// SignInForm.tsx - Pass checkbox state to login
const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    // ...
    try {
        await login(email, password, isChecked);  // NEW: Pass remember me state
        // ... rest of function

Impact: Checkbox now functional - unchecked = 1 hour, checked = 20 days.


4. Removed Session Contamination Checks

File: backend/igny8_core/auth/middleware.py

REMOVED ~50 lines of problematic code:

# DELETED CODE (lines ~67-116):
# Store account_id and user_id in session for contamination detection
if request.account:
    stored_account_id = request.session.get('_account_id')
    if stored_account_id and stored_account_id != request.account.id:
        return self._deny_request(request, 'Session contamination...', 401)
    request.session['_account_id'] = request.account.id

stored_user_id = request.session.get('_user_id')
if stored_user_id and stored_user_id != request.user.id:
    return self._deny_request(request, 'Session contamination...', 401)
request.session['_user_id'] = request.user.id

Replaced with simple comment:

# REMOVED: Session contamination checks on every request.
# These were causing random logouts - session integrity handled by Django

Impact: PRIMARY FIX - Eliminated false-positive logouts caused by timing issues and race conditions in session ID validation.


5. Logout Tracking System (Already Implemented)

Files:

  • backend/igny8_core/auth/middleware.py
  • frontend/src/services/api.ts
  • frontend/src/store/authStore.ts
  • frontend/src/components/auth/SignInForm.tsx

Features:

  • Backend sends structured logout reasons with error codes
  • Frontend captures and stores logout reasons before clearing auth state
  • SignIn page displays yellow alert with logout reason and expandable technical details
  • Prevents duplicate logging (React StrictMode handling)
  • Automatic vs manual logout prioritization

Impact: When logouts do occur, users now see clear explanations.


Authentication Flow Summary

Login Flow (Now)

  1. User enters credentials + checks "Keep me logged in" (optional)
  2. Frontend sends { email, password, remember_me: true/false } to /api/v1/auth/login/
  3. Backend validates credentials
  4. Backend generates access token:
    • remember_me=false: Expires in 1 hour
    • remember_me=true: Expires in 20 days
  5. Frontend stores token in localStorage + Zustand store
  6. User redirected to destination

Request Authentication Flow (Now)

  1. Frontend sends request with Authorization: Bearer <token> header
  2. DRF authentication classes try in order:
    • APIKeyAuthentication (for WordPress integration)
    • JWTAuthentication (checks token validity + expiry)
    • SessionAuthentication (fallback to session cookies)
    • BasicAuthentication (last resort)
  3. Middleware NO LONGER runs session contamination checks
  4. Middleware validates account/plan status (for non-superusers)
  5. Request.account set for multi-tenancy filtering

Token Refresh Flow (Unchanged)

  1. 401 received from backend
  2. Frontend checks for logout_reason in response
  3. If no logout_reason, attempts token refresh using refresh token
  4. If refresh succeeds, retries original request
  5. If refresh fails, logs out user

Validation Bypasses

The following users are exempt from account/plan validation:

  1. Superusers (is_superuser=True)
  2. Developers (role='developer')
  3. System Account Users (is_system_account_user() returns True)

This prevents admin users from being logged out due to account/plan issues.


Testing Checklist

Manual Testing Required

  1. Login without remember me → verify 1 hour expiry
  2. Login with remember me → verify 20 day expiry
  3. Verify no random logouts during normal usage
  4. Check browser dev tools: token payload includes remember_me field
  5. Test token refresh on 401 (without backend logout_reason)
  6. Test account/plan validation logout (with logout_reason display)
  7. Verify superuser can access without account/plan
  8. Test cross-account switching (if applicable)

Technical Verification

# 1. Check token expiry in Django shell (requires Docker or venv)
python manage.py shell -c "
from igny8_core.auth.utils import get_access_token_expiry
print('Default:', get_access_token_expiry(remember_me=False))
print('Remember me:', get_access_token_expiry(remember_me=True))
"

# 2. Check token payload in browser console after login
localStorage.getItem('auth-storage')
// Look for token field, decode JWT at jwt.io to see payload

# 3. Monitor middleware logs for logout events
docker-compose logs -f backend | grep "AUTO-LOGOUT"

Files Modified

Backend

  1. backend/igny8_core/settings.py - Token expiry settings
  2. backend/igny8_core/auth/utils.py - Remember me support in token generation
  3. backend/igny8_core/auth/serializers.py - Added remember_me field
  4. backend/igny8_core/auth/urls.py - LoginView updated to handle remember_me
  5. backend/igny8_core/auth/middleware.py - Removed session contamination checks

Frontend

  1. frontend/src/store/authStore.ts - Login function accepts rememberMe parameter
  2. frontend/src/components/auth/SignInForm.tsx - Passes checkbox state to login
  3. frontend/src/services/api.ts - Already updated (logout tracking)

Expected Results

Before Revamp

  • Users logged out every 15 minutes
  • Random logouts due to session contamination false positives
  • No user control over session length
  • No visibility into logout causes

After Revamp

  • Users stay logged in for 1 hour (or 20 days with remember me)
  • No more false-positive logouts from session validation
  • User control via "Keep me logged in" checkbox
  • Clear visibility when legitimate logouts occur (account issues, etc.)

Migration Notes

Existing Users

  • Users logged in before this update will continue to use old 15-minute tokens until they expire
  • After expiry, they'll get new 1-hour tokens on next login
  • No database migrations required (settings-only change)

Deployment

  1. Deploy backend changes first (settings + utils + serializers + urls + middleware)
  2. Deploy frontend changes (authStore + SignInForm)
  3. Clear any cached tokens (optional - they'll expire naturally)

Monitoring

  • Watch for any increase in 401 errors (shouldn't happen - tokens more permissive now)
  • Monitor AUTO-LOGOUT logs for legitimate account/plan issues
  • Check user feedback for remaining logout issues (should be drastically reduced)

Remaining Work (Optional Enhancements)

Short-term

  1. Check for cross-account AWS access contamination (user mentioned in feedback)
  2. Audit all API endpoints for unnecessary authentication checks
  3. Review ProtectedRoute component for excessive re-validation

Long-term

  1. Session cookie age could be dynamic based on remember_me (currently static 1 hour)
  2. Consider "remember this device" feature with device fingerprinting
  3. Add user preference for default remember me state
  4. Implement "Sign out all devices" functionality

Conclusion

The authentication system has been fundamentally improved by:

  1. Removing the root cause of random logouts (session contamination checks)
  2. Extending token lifetimes to reduce authentication friction (1 hour default, 20 days optional)
  3. Giving users control via remember me checkbox
  4. Maintaining visibility via logout tracking system

Users should now experience drastically fewer random logouts while retaining security through proper token expiration and validation.