Files
mizan/docs/MWT_SPEC.md

2.7 KiB

MWT — Mizan Web Token

Decided 2026-04-06.

MWT is a standard JWT (RFC 7519, HMAC-SHA256) with Mizan-specific claims, traveling on the X-Mizan-Token header. It is the protocol's identity layer for cache keying and permission staleness detection.

Claims

Claim Purpose
sub User ID — goes into HMAC cache key derivation
pkey Deterministic hash of user's permission state at issuance
exp Configurable short TTL — controls permission staleness window (Django setting)
iat Issued at
kid Key ID — for secret rotation. Carried in the JOSE header (RFC 7515), not the payload
aud Audience binding — prevents cross-tenant replay
nbf Not-before — tolerates clock skew
staff / super is_staff / is_superuser, used to build MWTUser without a DB query

Key decisions

  • Standard JWT envelope, not proprietary. Uses standard libraries for signing and validation.
  • X-Mizan-Token header, not Authorization: Bearer. Avoids collision with DRF, allauth, and existing JWT systems. Cloudflare WAF/Access do not inspect custom headers.
  • MWTUser is a minimal, DB-free request user built from the token claims (cores/mizan-python/src/mizan_core/mwt.py).

    A separate JWT module (mizan/jwt/) still exists for standard user-auth access/refresh tokens; MWT is the cache-keying identity layer, not a replacement for that module.

  • App handles authentication (session, social, etc.). Mizan issues MWT from the authenticated identity (create_mwt(user, secret, ttl, audience, kid)).
  • Edge Worker validates MWT, extracts sub for HMAC cache key, checks exp.
  • pkey computation must be deterministic: sorted(user.get_all_permissions()) then hash.
  • Client-side: proactive refresh before expiry. Check TTL before dispatch, not reactively after a 401.
  • Header-based, not cookie-based. A cookie would force Vary: Cookie, destroying PSR cache.

HMAC canonical form

JSON with sorted keys:

ctx:{context}:HMAC(secret, JSON.stringify({"c": context, "p": sorted_params, "r": rev, "u": user_id}))

See CACHE_KEYING.md for the full derivation.

What this solves

  • DRF token collision
  • JWTUser-too-thin problem
  • Permission staleness race condition
  • Single validation path across Python and TypeScript Edge

Usage rule

MWT is the identity Edge/cache layers key on. The @client(auth=...) parameter is enforced server-side in mizan/client/executor.py (_check_auth_requirement), which checks request.user against the auth requirement (required / staff / superuser / callable); request.user may be an MWTUser (stateless) or a session user.