# Authentication & Authorization **Last Verified:** December 25, 2025 **Backend Path:** `backend/igny8_core/auth/` **Frontend Path:** `frontend/src/store/authStore.ts` --- ## Quick Reference | What | File | Key Functions | |------|------|---------------| | User Model | `auth/models.py` | `User`, `Account`, `Plan` | | Auth Views | `auth/views.py` | `LoginView`, `RegisterView`, `RefreshTokenView` | | Middleware | `auth/middleware.py` | `AccountContextMiddleware` | | JWT Auth | `api/authentication.py` | `JWTAuthentication`, `CookieJWTAuthentication` | | API Key Auth | `api/authentication.py` | `APIKeyAuthentication` | | Frontend Store | `store/authStore.ts` | `useAuthStore` | --- ## Authentication Methods ### 1. JWT Token Authentication (Primary) **Flow:** 1. User logs in via `/api/v1/auth/login/` 2. Backend returns `access_token` (15 min) + `refresh_token` (7 days) 3. Frontend stores tokens in localStorage and Zustand store 4. All API requests include `Authorization: Bearer ` 5. Token refresh via `/api/v1/auth/token/refresh/` **Token Payload:** ```json { "user_id": 123, "account_id": 456, "email": "user@example.com", "exp": 1735123456, "iat": 1735122456 } ``` ### 2. Session Authentication (Admin/Fallback) - Used by Django Admin interface - Cookie-based session with CSRF protection - Redis-backed sessions (prevents user swapping bug) ### 3. API Key Authentication (WordPress Bridge) **Flow:** 1. Account generates API key in settings 2. WordPress plugin uses `Authorization: ApiKey ` 3. Backend validates key, sets `request.account` and `request.site` **Use Cases:** - WordPress content sync - External integrations - Headless CMS connections --- ## API Endpoints | Method | Path | Handler | Purpose | |--------|------|---------|---------| | POST | `/api/v1/auth/register/` | `RegisterView` | Create new user + account | | POST | `/api/v1/auth/login/` | `LoginView` | Authenticate, return tokens | | POST | `/api/v1/auth/logout/` | `LogoutView` | Invalidate tokens | | POST | `/api/v1/auth/token/refresh/` | `RefreshTokenView` | Refresh access token | | POST | `/api/v1/auth/password/change/` | `ChangePasswordView` | Change password | | POST | `/api/v1/auth/password/reset/` | `RequestPasswordResetView` | Request reset email | | POST | `/api/v1/auth/password/reset/confirm/` | `ResetPasswordView` | Confirm reset with token | --- ## User Roles | Role | Code | Permissions | |------|------|-------------| | **Developer** | `developer` | Full access across ALL accounts (superuser) | | **Admin** | `admin` | Full access to own account | | **Manager** | `manager` | Manage content, view billing | | **Editor** | `editor` | AI content, manage clusters/tasks | | **Viewer** | `viewer` | Read-only dashboards | | **System Bot** | `system_bot` | System automation (internal) | **Role Hierarchy:** ``` developer > admin > manager > editor > viewer ``` --- ## Middleware: AccountContextMiddleware **File:** `auth/middleware.py` **Purpose:** Injects `request.account` on every request **Flow:** 1. Check for JWT token → extract account_id 2. Check for session → get account from session 3. Check for API key → get account from key 4. Validate account exists and is active 5. Validate plan exists and is active 6. Set `request.account`, `request.user` **Error Responses:** - No account: 403 with JSON error - Inactive plan: 402 with JSON error --- ## Frontend Auth Store **File:** `store/authStore.ts` **State:** ```typescript { user: User | null; token: string | null; refreshToken: string | null; isAuthenticated: boolean; } ``` **Actions:** - `login(email, password)` - Authenticate and store tokens - `register(data)` - Create account and store tokens - `logout()` - Clear tokens and reset stores - `refreshToken()` - Refresh access token - `checkAuth()` - Verify current auth state **Critical Implementation:** ```typescript // Tokens are written synchronously to localStorage // This prevents race conditions where API calls happen before persist localStorage.setItem('auth-storage', JSON.stringify(authState)); ``` --- ## Session Security (Redis-Backed) **Problem Solved:** User swapping / random logout issues **Implementation:** ```python # settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.cache' SESSION_CACHE_ALIAS = 'default' # Redis # auth/backends.py class NoCacheModelBackend(ModelBackend): """Authentication backend without user caching""" pass ``` **Session Integrity:** - Stores `account_id` and `user_id` in session - Validates on every request - Prevents cross-request contamination --- ## API Key Management **Model:** `APIKey` in `auth/models.py` | Field | Type | Purpose | |-------|------|---------| | key | CharField | Hashed API key | | account | ForeignKey | Owner account | | site | ForeignKey | Optional: specific site | | name | CharField | Key name/description | | is_active | Boolean | Enable/disable | | created_at | DateTime | Creation time | | last_used_at | DateTime | Last usage time | **Generation:** - 32-character random key - Stored hashed (SHA-256) - Shown once on creation --- ## Permission Checking **In ViewSets:** ```python class MyViewSet(AccountModelViewSet): permission_classes = [IsAuthenticated] def get_queryset(self): # Automatically filtered by request.account return super().get_queryset() ``` **Role Checks:** ```python if request.user.is_admin_or_developer: # Admin/developer access pass elif request.user.role == 'editor': # Editor access pass ``` --- ## Logout Flow **Backend:** 1. Blacklist refresh token (if using token blacklist) 2. Clear session **Frontend (Critical):** ```typescript logout: () => { // NEVER use localStorage.clear() - breaks Zustand persist const authKeys = ['auth-storage', 'site-storage', 'sector-storage', 'billing-storage']; authKeys.forEach(key => localStorage.removeItem(key)); // Reset dependent stores useSiteStore.setState({ activeSite: null }); useSectorStore.setState({ activeSector: null, sectors: [] }); set({ user: null, token: null, isAuthenticated: false }); } ``` --- ## Common Issues | Issue | Cause | Fix | |-------|-------|-----| | 403 after login | Tokens not persisted before API call | Write to localStorage synchronously | | User swapping | DB-backed sessions with user caching | Redis sessions + NoCacheModelBackend | | Token refresh loop | Refresh token expired | Redirect to login | | API key not working | Missing site scope | Check API key has correct site assigned | --- ## Planned Changes | Feature | Status | Description | |---------|--------|-------------| | Token blacklist | 🔜 Planned | Proper refresh token invalidation | | 2FA | 🔜 Planned | Two-factor authentication | | SSO | 🔜 Planned | Google/GitHub OAuth |