6.7 KiB
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:
- User logs in via
/api/v1/auth/login/ - Backend returns
access_token(15 min) +refresh_token(7 days) - Frontend stores tokens in localStorage and Zustand store
- All API requests include
Authorization: Bearer <access_token> - 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:
- Account generates API key in settings
- WordPress plugin uses
Authorization: ApiKey <key> - Backend validates key, sets
request.accountandrequest.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:
- Check for JWT token → extract account_id
- Check for session → get account from session
- Check for API key → get account from key
- Validate account exists and is active
- Validate plan exists and is active
- 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 tokensregister(data)- Create account and store tokenslogout()- Clear tokens and reset storesrefreshToken()- Refresh access tokencheckAuth()- 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_idanduser_idin 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:
- Blacklist refresh token (if using token blacklist)
- 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 |