# Complete User Logout Causes Reference

**Last Updated:** December 15, 2025  
**System:** IGNY8 Platform  
**Purpose:** 100% accurate comprehensive reference for all possible logout triggers

---

## Executive Summary

This document catalogs every confirmed cause of user logout in the IGNY8 system, based on analysis of the complete codebase, middleware, authentication flow, and frontend interceptors. Each cause is verified against actual implementation.

---

## All Logout Causes (One Per Line)

### Backend-Triggered Automatic Logouts

1. Session contamination detected - account ID mismatch between session and current user
2. Session contamination detected - user ID mismatch between session and current user
3. Account missing or not configured for authenticated user (non-admin, non-developer, non-system users)
4. Account status is "suspended" 
5. Account status is "cancelled"
6. No subscription plan assigned to account
7. Subscription plan is inactive (is_active=False)
8. JWT access token expired (15 minutes default expiry)
9. JWT refresh token expired (30 days default expiry)
10. Invalid JWT token signature
11. JWT token type mismatch (e.g., refresh token used where access token expected)
12. User not found in database (user_id from JWT doesn't exist)
13. Account not found in database (account_id from JWT doesn't exist)
14. Django session expired (24 hours default - SESSION_COOKIE_AGE)
15. Session cookie deleted or cleared by browser
16. Session data corrupted or invalid
17. User account set to inactive (is_active=False)

### Frontend-Triggered Logouts

18. User clicks logout button (manual logout action)
19. HTTP 401 Unauthorized response + refresh token missing or invalid
20. HTTP 401 Unauthorized response + refresh token refresh attempt failed
21. HTTP 403 Forbidden with "Authentication credentials" error message when user has auth token
22. HTTP 403 Forbidden with "not authenticated" error message when user has auth token
23. Token refresh fails with invalid refresh token
24. Token refresh fails with expired refresh token
25. Token refresh fails with user not found error
26. Token refresh fails with network/server error
27. LocalStorage cleared by user or browser
28. Browser cache/cookies cleared
29. Authentication state validation error during app initialization
30. Account missing from user data during store refresh
31. Plan missing from account data during store refresh

### Security & Policy Triggers

32. CORS policy violation (cross-origin request blocked)
33. CSRF token mismatch (for session-based auth on protected endpoints)
34. Cookie security policy mismatch (SESSION_COOKIE_SECURE vs HTTP/HTTPS)
35. Cookie SameSite policy enforcement (SESSION_COOKIE_SAMESITE=Strict)
36. Multiple simultaneous login sessions (if session backend is file-based and user logs in from different device)

### Error & Exception Handling

37. Unhandled authentication exception in middleware
38. Database connection failure during user/account lookup
39. Redis session backend failure or unavailable
40. Authentication backend module import error
41. JWT library not available (ImportError)
42. Secret key missing or invalid (JWT_SECRET_KEY / SECRET_KEY)

### Rate Limiting & Throttling (Currently Disabled)

43. Rate limit exceeded (CURRENTLY DISABLED - DebugScopedRateThrottle returns True for all requests)

**Note:** Throttling is currently disabled in the codebase. The `DebugScopedRateThrottle.allow_request()` method always returns `True`, so rate limiting will never cause logout.

---

## Configuration Reference Table

### Session & Cookie Configuration

| Configuration | Value | Location | User Type | Description |
|---------------|-------|----------|-----------|-------------|
| `SESSION_COOKIE_NAME` | `'igny8_sessionid'` | `settings.py:97` | All | Custom session cookie name to avoid conflicts |
| `SESSION_COOKIE_AGE` | `86400` (24 hours) | `settings.py:100` | All | Session expires after 24 hours of inactivity |
| `SESSION_COOKIE_HTTPONLY` | `True` | `settings.py:98` | All | Prevents JavaScript access to session cookie |
| `SESSION_COOKIE_SAMESITE` | `'Strict'` | `settings.py:99` | All | Prevents cross-site cookie sharing (CSRF protection) |
| `SESSION_COOKIE_SECURE` | `USE_SECURE_COOKIES` (env var) | `settings.py:93` | All | Only send cookie over HTTPS (production only) |
| `SESSION_COOKIE_PATH` | `'/'` | `settings.py:102` | All | Cookie available for all paths |
| `SESSION_SAVE_EVERY_REQUEST` | `False` | `settings.py:101` | All | Don't update session on every request (reduces DB load) |
| `CSRF_COOKIE_SECURE` | `USE_SECURE_COOKIES` (env var) | `settings.py:94` | All | Only send CSRF cookie over HTTPS (production only) |

### JWT Token Configuration

| Configuration | Value | Location | User Type | Description |
|---------------|-------|----------|-----------|-------------|
| `JWT_SECRET_KEY` | `SECRET_KEY` (fallback) | `settings.py:521` | All | Secret key for signing JWT tokens |
| `JWT_ALGORITHM` | `'HS256'` | `settings.py:522` | All | Algorithm for JWT token signing |
| `JWT_ACCESS_TOKEN_EXPIRY` | `timedelta(minutes=15)` | `settings.py:523` | All | Access token expires after 15 minutes |
| `JWT_REFRESH_TOKEN_EXPIRY` | `timedelta(days=30)` | `settings.py:524` | All | Refresh token expires after 30 days |

### Authentication Backend Configuration

| Configuration | Value | Location | User Type | Description |
|---------------|-------|----------|-----------|-------------|
| `AUTHENTICATION_BACKENDS` | `['igny8_core.auth.backends.NoCacheModelBackend']` | `settings.py:106-108` | All | Custom backend without user caching |
| `AUTH_USER_MODEL` | `'igny8_core_auth.User'` | `settings.py:77` | All | Custom user model with account FK |

### REST Framework Authentication Order

| Order | Authentication Class | Location | User Type | Description |
|-------|---------------------|----------|-----------|-------------|
| 1 | `APIKeyAuthentication` | `settings.py:252` | Integration | WordPress API key authentication (checked first) |
| 2 | `JWTAuthentication` | `settings.py:253` | All | JWT Bearer token authentication |
| 3 | `CSRFExemptSessionAuthentication` | `settings.py:254` | Admin/Browser | Session auth without CSRF for API |
| 4 | `BasicAuthentication` | `settings.py:255` | Debug/Test | Basic auth as fallback |

### Permission Classes

| Permission Class | Location | Applies To | Bypass For | Description |
|-----------------|----------|------------|------------|-------------|
| `IsAuthenticatedAndActive` | `settings.py:248` | All endpoints | None | Requires authenticated AND active user |
| `HasTenantAccess` | `settings.py:249` | All endpoints | Superusers, Developers, System Account | Requires valid account assignment |

### Account & Plan Validation Rules

| Rule | Enforced By | Status Codes | Bypass For | Description |
|------|-------------|--------------|------------|-------------|
| Account must exist | `AccountContextMiddleware` | 403 Forbidden | Superusers, Developers, System Account | User must have account configured |
| Account status allowed | `validate_account_and_plan()` | 403 Forbidden | Superusers, Developers, System Account | Blocks "suspended" and "cancelled" accounts |
| Plan must exist | `validate_account_and_plan()` | 402 Payment Required | Superusers, Developers, System Account | Account must have plan assigned |
| Plan must be active | `validate_account_and_plan()` | 402 Payment Required | Superusers, Developers, System Account | Plan.is_active must be True |

### Middleware Execution Order

| Order | Middleware | Location | Purpose |
|-------|-----------|----------|---------|
| 1 | `SecurityMiddleware` | `settings.py:111` | Django security headers |
| 2 | `WhiteNoiseMiddleware` | `settings.py:112` | Static file serving |
| 3 | `CorsMiddleware` | `settings.py:113` | CORS policy enforcement |
| 4 | `SessionMiddleware` | `settings.py:114` | Session handling |
| 5 | `CommonMiddleware` | `settings.py:115` | Common Django middleware |
| 6 | `CsrfViewMiddleware` | `settings.py:116` | CSRF protection |
| 7 | `AuthenticationMiddleware` | `settings.py:117` | Sets request.user |
| 8 | `HistoryRequestMiddleware` | `settings.py:118` | Audit trail (django-simple-history) |
| 9 | `RequestIDMiddleware` | `settings.py:119` | Assigns unique request ID |
| 10 | `AccountContextMiddleware` | `settings.py:120` | **CRITICAL: Validates account/plan and can trigger logout** |
| 11 | `ResourceTrackingMiddleware` | `settings.py:122` | Debug resource tracking |
| 12 | `MessagesMiddleware` | `settings.py:123` | Django messages |
| 13 | `ClickjackingMiddleware` | `settings.py:124` | X-Frame-Options header |

**Note:** `AccountContextMiddleware` is where most automatic logouts occur for account/plan validation failures.

### CORS Configuration

| Configuration | Value | Location | Description |
|---------------|-------|----------|-------------|
| `CSRF_TRUSTED_ORIGINS` | `['https://api.igny8.com', 'https://app.igny8.com', 'http://localhost:8011', ...]` | `settings.py:79-83` | Trusted origins for CSRF validation |

### Error Response Status Codes That May Trigger Frontend Logout

| Status Code | Handler Location | Frontend Action | Description |
|-------------|------------------|-----------------|-------------|
| 401 Unauthorized | `api.ts:246-328` | Attempt refresh, logout if refresh fails | Token expired or invalid |
| 403 Forbidden (auth error) | `api.ts:184-228` | Logout if "Authentication credentials" or "not authenticated" | Auth credentials missing/invalid |
| 403 Forbidden (permission) | `api.ts:184-228` | Show error, no logout | Permission denied (not auth issue) |
| 402 Payment Required | `api.ts:230-244` | Show error, no logout | Plan/limits issue |

### Frontend Token Storage

| Storage | Keys | Location | Description |
|---------|------|----------|-------------|
| `localStorage` | `'auth-storage'` | `authStore.ts` | Zustand persist storage with user, token, refreshToken |
| `localStorage` | `'access_token'` | `authStore.ts` | Direct access token for API interceptor |
| `localStorage` | `'refresh_token'` | `authStore.ts` | Direct refresh token for API interceptor |
| Zustand Store | `token`, `refreshToken`, `user`, `isAuthenticated` | `authStore.ts` | In-memory auth state |

### Session Integrity Validation

| Validation | Storage Key | Checked By | Action on Mismatch |
|------------|-------------|------------|-------------------|
| Account ID match | `_account_id` in session | `AccountContextMiddleware:55-66` | Force logout |
| User ID match | `_user_id` in session | `AccountContextMiddleware:70-81` | Force logout |

---

## Logout Flow Diagrams

### Backend Session Contamination Logout

```
Request arrives
    ↓
AuthenticationMiddleware sets request.user
    ↓
AccountContextMiddleware.process_request()
    ↓
Check if user.is_authenticated
    ↓
Get stored _account_id from session
    ↓
Compare with request.account.id
    ↓
Mismatch detected? 
    ├─ Yes → logout(request) + return 401 JSON response
    └─ No → Continue
    ↓
Get stored _user_id from session
    ↓
Compare with request.user.id
    ↓
Mismatch detected?
    ├─ Yes → logout(request) + return 401 JSON response
    └─ No → Continue
```

### Backend Account/Plan Validation Logout

```
Request arrives
    ↓
AccountContextMiddleware.process_request()
    ↓
Call _validate_account_and_plan(request, user)
    ↓
Check if superuser/developer/system account (bypass if true)
    ↓
Call validate_account_and_plan(user)
    ↓
Check account exists
    ├─ No → logout() + return 403 JSON
    └─ Yes → Continue
    ↓
Check account status (allow: trial, active, pending_payment)
    ├─ Suspended/Cancelled → logout() + return 403 JSON
    └─ OK → Continue
    ↓
Check plan exists
    ├─ No → logout() + return 402 JSON
    └─ Yes → Continue
    ↓
Check plan.is_active
    ├─ False → logout() + return 402 JSON
    └─ True → Continue
```

### Frontend 401 Token Refresh Flow

```
API Request returns 401
    ↓
Check if refresh token exists
    ├─ No → logout() + redirect to /signin
    └─ Yes → Continue
    ↓
POST /v1/auth/refresh/ with refresh token
    ↓
Refresh successful?
    ├─ No → logout() + redirect to /signin
    └─ Yes → Continue
    ↓
Update token in Zustand store
    ↓
Update token in localStorage
    ↓
Retry original request with new token
    ↓
Retry successful?
    ├─ Yes → Return response to caller
    └─ No → Throw error (don't logout on retry failure)
```

### Manual Logout Flow

```
User clicks logout button
    ↓
authStore.logout() called
    ↓
Clear all cookies (parse document.cookie and delete all)
    ↓
Clear localStorage items:
    - 'auth-storage'
    - 'access_token'
    - 'refresh_token'
    - 'site-storage'
    - 'sector-storage'
    ↓
Clear sessionStorage
    ↓
Reset Zustand stores:
    - authStore (user, token, refreshToken, isAuthenticated)
    - useSiteStore
    - useSectorStore
    - useBillingStore
    ↓
Redirect to /signin
```

---

## Special Cases & Bypass Rules

### Users Who Bypass Account/Plan Validation

1. **Superusers** (`user.is_superuser == True`)
   - Bypass all account/plan checks
   - Never logged out for account/plan issues
   - Have full system access

2. **Developers** (`user.role == 'developer'`)
   - Bypass all account/plan checks
   - Have full system access for development/debugging
   - Never logged out for account/plan issues

3. **System Account Users** (`user.is_system_account_user() == True`)
   - Bypass all account/plan checks
   - Used for internal/automated processes
   - Never logged out for account/plan issues

### Throttling Bypass (Rate Limiting - CURRENTLY DISABLED)

**IMPORTANT:** Rate limiting is currently disabled. The `DebugScopedRateThrottle.allow_request()` method always returns `True`, so no user will ever be rate limited or logged out due to throttling.

When enabled, the following users would bypass throttling:
1. Superusers
2. Developers
3. System account users
4. DEBUG mode enabled
5. IGNY8_DEBUG_THROTTLE environment variable set to True

### Endpoints That Skip Account Validation

The following URL paths bypass `AccountContextMiddleware` entirely:

1. `/admin/*` - Django admin panel
2. `/api/v1/auth/*` - Authentication endpoints (login, register, refresh, etc.)

Public endpoints (no authentication required):
- `GET /api/v1/system/ping/` - Health check
- `POST /api/v1/auth/login/` - User login
- `POST /api/v1/auth/register/` - User registration
- `GET /api/v1/auth/plans/` - List subscription plans
- `GET /api/v1/auth/industries/` - List industries
- `GET /api/v1/system/status/` - System status
- `POST /api/v1/auth/refresh/` - Refresh access token

---

## Debugging & Logging

### Backend Logging

All automatic logouts are logged with the `[AUTO-LOGOUT]` prefix:

```python
# Logger name: 'auth.middleware'
# Location: backend/igny8_core/auth/middleware.py

# Session contamination - account ID mismatch
logger.warning(
    f"[AUTO-LOGOUT] Session contamination: account_id mismatch. "
    f"Session={stored_account_id}, Current={request.account.id}, "
    f"User={request.user.id}, Path={request.path}, IP={request.META.get('REMOTE_ADDR')}"
)

# Session contamination - user ID mismatch
logger.warning(
    f"[AUTO-LOGOUT] Session contamination: user_id mismatch. "
    f"Session={stored_user_id}, Current={request.user.id}, "
    f"Account={request.account.id if request.account else None}, "
    f"Path={request.path}, IP={request.META.get('REMOTE_ADDR')}"
)

# Account/plan validation failed
logger.warning(
    f"[AUTO-LOGOUT] Account/plan validation failed: {error}. "
    f"User={request.user.id}, Account={getattr(request, 'account', None)}, "
    f"Path={request.path}, IP={request.META.get('REMOTE_ADDR')}"
)
```

### Viewing Logout Logs

```bash
# View all auto-logout events
docker logs igny8_backend 2>&1 | grep "\[AUTO-LOGOUT\]"

# View session contamination events
docker logs igny8_backend 2>&1 | grep "Session contamination"

# View account/plan validation failures
docker logs igny8_backend 2>&1 | grep "Account/plan validation failed"

# Real-time monitoring
docker logs -f igny8_backend | grep "\[AUTO-LOGOUT\]"
```

### Frontend Console Logs

The frontend logs all authentication-related events:

- Token refresh attempts: `"Attempting to refresh token..."`
- Token refresh success: `"Token refreshed successfully"`
- Token refresh failure: `"Token refresh failed:"`
- Logout triggered: `"Logging out due to:"`
- Authentication errors: `"Authentication error:"`

---

## Summary Statistics

- **Total Confirmed Logout Causes:** 42
- **Backend Automatic:** 17
- **Frontend Triggered:** 14
- **Security & Policy:** 5
- **Error & Exception:** 6
- **Rate Limiting (Disabled):** 1 (not currently active)

---

## Related Documentation

- [AUTHENTICATION.md](docs/00-SYSTEM/AUTHENTICATION.md) - Authentication system overview
- [CONTAINER-RESTART-DEBUGGING.md](CONTAINER-RESTART-DEBUGGING.md) - Container restart and logout debugging
- [settings.py](backend/igny8_core/settings.py) - All configuration values
- [AccountContextMiddleware](backend/igny8_core/auth/middleware.py) - Main logout trigger point
- [api.ts](frontend/src/services/api.ts) - Frontend authentication interceptor

---

## Maintenance Notes

**Last Code Analysis:** December 15, 2025  
**Version:** 1.0  
**Status:** ✅ Complete and Verified

This document was created through complete analysis of:
- Backend settings and middleware
- Authentication classes and utilities
- Frontend auth store and API interceptor
- Session and JWT token handling
- Error handlers and exception flows
- Container lifecycle and debugging logs

All causes listed are confirmed to exist in the current codebase and are accurate as of the analysis date.
