๐ Authentication Setup (Not Permissions) โ
โ ๏ธ Important:
This document covers authentication โ verifying who a user is.
It does not cover permissions, which define what the user can access or do.
See the Permissions Guide for data permissions.
๐ง Authentication vs Permissions โ
Authentication answers: "Who are you?"
e.g., validating a token or session.Permissions answer: "Are you allowed to do this?"
e.g., can you view this object? edit this field?
StateZero handles permissions internally, but authentication is delegated to Django REST Framework (DRF).
๐ Outer Defense: STATEZERO_VIEW_ACCESS_CLASS
โ
StateZero uses Django-style permission classes to guard access to its own endpoints.
This is defined via:
# settings.py
STATEZERO_VIEW_ACCESS_CLASS = "rest_framework.permissions.IsAuthenticated"
This setting is like the castle gate โ it defines who can even make requests to a StateZero endpoint. It is the outer defense layer, enforced before any actual request processing occurs.
- If a request fails this check, StateZero will not process it at all.
- This gate applies to all StateZero routes: schema discovery, model reads/writes, file uploads, event subscriptions, etc.
- โ
You can use classes like
IsAuthenticated
,AllowAny
,IsAdminUser
, orIsStaffUser
. - โ Do not use DRF permission classes like
IsAuthenticatedOrReadOnly
,DjangoModelPermissionsOrAnonReadOnly
, or others that assume REST-style verb-based logic. StateZero works with abstract operations, not HTTP verbs โ these mixed-mode permission classes will not behave as intended.
๐ How Authentication Works โ
Backend Authentication Use DRF authentication: Token, Session, JWT, etc.
Frontend Configuration Provide a
getAuthHeaders()
function in your StateZero config.Automatic Header Injection StateZero includes those headers in every request.
โก State Reset on Auth Changes Call
resetStateZero()
when users log in/out to ensure clean state.
๐งฑ Django Backend Setup โ
In settings.py
:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
STATEZERO_VIEW_ACCESS_CLASS = "rest_framework.permissions.IsAuthenticated"
Install DRF token support:
INSTALLED_APPS = [
# ... other apps
'rest_framework.authtoken',
]
Run migrations:
python manage.py migrate
Add a token login endpoint:
# urls.py
from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
path('api/token/', obtain_auth_token, name='api_token_auth'),
# ... other URLs
]
๐ป Frontend Configuration โ
Basic example using localStorage:
// statezero.config.js
function getAuthToken() {
return localStorage.getItem('auth_token');
}
export default {
backendConfigs: {
default: {
API_URL: "http://127.0.0.1:8000/statezero",
GENERATED_TYPES_DIR: "./src/models/",
GENERATED_ACTIONS_DIR: "./src/actions/",
fileUploadMode: "server",
BACKEND_TZ: "UTC",
// Inject the auth header for all requests
getAuthHeaders: () => {
const token = getAuthToken();
return token ? { Authorization: `Token ${token}` } : {};
},
events: {
type: "pusher",
pusher: {
clientOptions: {
appKey: "your_pusher_app_key",
cluster: "your_pusher_cluster",
forceTLS: true,
authEndpoint: "http://127.0.0.1:8000/statezero/events/auth/",
},
},
},
},
},
};
โก StateZero Reset on Authentication Changes โ
Critical: Always call resetStateZero()
when authentication state changes to prevent data leaks between users.
import { resetStateZero } from '@statezero/core';
// โ
ALWAYS reset StateZero after login/logout
const login = async (username, password) => {
const response = await fetch('/api/auth/login/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
const data = await response.json();
if (response.ok) {
localStorage.setItem('auth_token', data.token);
// ๐ฅ CRITICAL: Reset StateZero for clean authenticated state
await resetStateZero();
return true;
}
return false;
};
const logout = async () => {
await fetch('/api/auth/logout/', {
method: 'POST',
headers: { 'Authorization': `Token ${getAuthToken()}` },
});
localStorage.removeItem('auth_token');
// ๐ฅ CRITICAL: Reset StateZero to clear user data
await resetStateZero();
};
Why Reset StateZero? โ
resetStateZero()
performs a complete cleanup:
- ๐๏ธ Clears all stores: QuerySet, Model, and Metric registries
- ๐งน Removes cached data: Prevents data from previous user sessions
- ๐ Reconnects events: Re-establishes real-time connections with fresh auth
- โก Resets operations: Clears all pending optimistic updates
- ๐๏ธ Fresh state: Ensures clean slate for new authenticated session
When to Call resetStateZero()
โ
Event | Why Reset? | Example |
---|---|---|
Login | Clean authenticated state | await resetStateZero() after setting token |
Logout | Clear all user data | await resetStateZero() after removing token |
Token refresh | Update auth headers | await resetStateZero() after token update |
Auth failure | Clear stale/invalid data | await resetStateZero() when 401/403 detected |
๐งช Full Login + Token Management Example โ
// auth.js
import { resetStateZero } from '@statezero/core';
export class AuthService {
constructor() {
this.baseURL = 'http://127.0.0.1:8000/api';
}
async login(username, password) {
const res = await fetch(`${this.baseURL}/token/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
if (!res.ok) throw new Error('Login failed');
const data = await res.json();
localStorage.setItem('auth_token', data.token);
// ๐ฅ Reset StateZero for clean authenticated state
await resetStateZero();
return data;
}
async logout() {
localStorage.removeItem('auth_token');
// ๐ฅ Reset StateZero to clear all user data
await resetStateZero();
}
getToken() {
return localStorage.getItem('auth_token');
}
isAuthenticated() {
return !!this.getToken();
}
async validateToken() {
const token = this.getToken();
if (!token) return false;
try {
const res = await fetch(`${this.baseURL}/validate/`, {
headers: { Authorization: `Token ${token}` }
});
if (!res.ok) {
// Token invalid - clear and reset
localStorage.removeItem('auth_token');
await resetStateZero();
return false;
}
return true;
} catch (error) {
// Network error - assume token invalid
localStorage.removeItem('auth_token');
await resetStateZero();
return false;
}
}
}
export const authService = new AuthService();
// statezero.config.js โ updated to use auth service
import { authService } from './auth.js';
export default {
backendConfigs: {
default: {
API_URL: "http://127.0.0.1:8000/statezero",
GENERATED_TYPES_DIR: "./src/models/",
GENERATED_ACTIONS_DIR: "./src/actions/",
fileUploadMode: "server",
BACKEND_TZ: "UTC",
getAuthHeaders: () => {
const token = authService.getToken();
return token ? { Authorization: `Token ${token}` } : {};
},
events: {
type: "pusher",
pusher: {
clientOptions: {
appKey: "your_pusher_app_key",
cluster: "your_pusher_cluster",
forceTLS: true,
authEndpoint: "http://127.0.0.1:8000/statezero/events/auth/",
},
},
},
},
},
};
โ Summary โ
- Authentication = verify who the user is
- Use DRF Token Auth on the backend
- Inject auth headers via
getAuthHeaders()
in the frontend STATEZERO_VIEW_ACCESS_CLASS
defines the outermost access gate- โ
Use simple DRF permission classes like
IsAuthenticated
,AllowAny
,IsAdminUser
,IsStaffUser
- โ Do not use
IsAuthenticatedOrReadOnly
,DjangoModelPermissionsOrAnonReadOnly
, or similar - StateZero automatically sends headers in all requests
- ๐ฅ CRITICAL: Always call
resetStateZero()
on login/logout to prevent data leaks
โ Authenticated users can now connect to StateZero ๐ What they can do or see is governed by permissions
๐ Continue Reading: Permissions Guide โ
Once authenticated, users must still pass fine-grained permissions to:
- Read or edit models
- View or write fields
- Perform object-level or bulk actions
๐ Learn how in the StateZero Permissions Guide