Skip to content

๐Ÿ” 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:

python
# 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, or IsStaffUser.
  • โŒ 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 โ€‹

  1. Backend Authentication Use DRF authentication: Token, Session, JWT, etc.

  2. Frontend Configuration Provide a getAuthHeaders() function in your StateZero config.

  3. Automatic Header Injection StateZero includes those headers in every request.

  4. โšก State Reset on Auth Changes Call resetStateZero() when users log in/out to ensure clean state.

๐Ÿงฑ Django Backend Setup โ€‹

In settings.py:

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

python
INSTALLED_APPS = [
    # ... other apps
    'rest_framework.authtoken',
]

Run migrations:

bash
python manage.py migrate

Add a token login endpoint:

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

js
// 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.

js
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() โ€‹

EventWhy Reset?Example
LoginClean authenticated stateawait resetStateZero() after setting token
LogoutClear all user dataawait resetStateZero() after removing token
Token refreshUpdate auth headersawait resetStateZero() after token update
Auth failureClear stale/invalid dataawait resetStateZero() when 401/403 detected

๐Ÿงช Full Login + Token Management Example โ€‹

js
// 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();
js
// 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