Files
igny8/docs/00-SYSTEM/AUTH-FLOWS.md
IGNY8 VPS (Salman) c777e5ccb2 dos updates
2026-01-20 14:45:21 +00:00

6.7 KiB

Authentication & Authorization

Last Verified: January 20, 2026
Version: 1.8.4
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 <access_token>
  5. Token refresh via /api/v1/auth/token/refresh/

Token Payload:

{
  "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 <key>
  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)
Owner owner Full access to own account (account creator)
Admin admin Full access to own account, billing, team management
Editor editor Content creation and editing only
Viewer viewer Read-only access to content
System Bot system_bot System automation (internal)

Role Hierarchy:

developer > owner > admin > 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:

{
  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:

// 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:

# 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:

class MyViewSet(AccountModelViewSet):
    permission_classes = [IsAuthenticated]
    
    def get_queryset(self):
        # Automatically filtered by request.account
        return super().get_queryset()

Role Checks:

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):

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