Authentication
Every CNS request authenticates via a Bearer JWT in the Authorization header —
never cookies. That single transport choice is deliberate: Bearer tokens aren't
auto-attached cross-origin the way cookies are, so classic CSRF is moot for this API
without needing a CSRF token at all (it would only re-enter the picture if CNS were
ever forced onto cookie auth).
There are two token shapes, used in different places:
| Token | Who holds it | Used on |
|---|---|---|
| SSO-JWT (end user) | A tenant's own IdP, verified by the identity adapter | Every route: POST /v1/events (as a subject principal), GET /v1/feed, GET /v1/stream, all Admin API routes |
| Service-JWT (app/publisher) | Minted by the app itself from its own ed25519 key | POST /v1/events only — no other route accepts it |
POST /v1/events is the one route that accepts either, and routes between them by a
cheap, unverified peek at the token (EdDSA algorithm + a fixed issuer claim,
trust.PeekIsServiceToken) purely to decide which verifier to run — that peek
grants no trust by itself; the real signature/claims verification always follows.
Every other data-plane route, and the entire Admin API, is end-user-only: an
app/publisher credential carries no subscribe/read or admin authority, by design (see
Authz planes).
The SSO-JWT adapter is IdP-shape-agnostic
The ssojwt identity adapter never hardcodes any one IdP. It's configured with:
- a signing-key source — either a static HMAC secret, or a JWKS URL (fetched, cached,
resolved by
kid); - issuer and audience to check;
- configurable claim names for tenant/email/phone, since not every IdP names them the same way;
- an optional static fallback tenant id, for an IdP whose tokens carry no tenant claim at all.
This is what makes wiring a non-native IdP — e.g. a tenant that authenticates via Supabase Auth instead of a central SSO — a configuration exercise, not a new code path.
Distinguishing "authenticated" from "authorized"
Authentication only answers "who are you." What you're allowed to do — publish, subscribe, or administer — is three separate decisions; see Authz planes.