The handbook
OverviewConnectingSecurity model
Getting started

Security model

The console is a thin client. It owns no backend, stores no identities, and proxies no traffic. Every request flows from your browser to the AcasoOS instance you connected to.

The security model rests on three primitives:

  • Session-key exchangelong-lived keys are traded for short-lived session tokens.
  • Strict CORSevery instance must whitelist the console's origin.
  • Cascading revocationrevoking the parent kills every derived session.

Session-key exchange

Long-lived API keys — each owned by an application — are the platform's most-privileged credential. The console never persists one. The exchange runs once per session and immediately downgrades the long-lived key to a short-lived child.

  1. Submit the form. You paste the AcasoOS URL and the long-lived admin key into the Connect form.
  2. Exchange the key. The console calls POST {url}/v1/auth/session with Authorization: Bearer <long-lived-key>.
  3. AcasoOS mints a child key. A new row lands in dh_api_keys with kind='session', parent_key_id=<long-lived-key.id>, inherited scopes, and expires_at = NOW() + 8h.
  4. Receive the session. The response is {session_token, expires_at, user, tenant}.
  5. Discard the long-lived key. It is never written to sessionStorage, localStorage, IndexedDB, or cookies.
  6. Persist the session. The console writes {url, name, session_token, expires_at, user, tenant} to sessionStorage (not localStorage).
  7. Authorize everything else. Every subsequent call sends session_token as the Bearer token.

Tip: Verify this yourself in DevTools → Application → Storage. The session token appears in sessionStorage; the long-lived key is absent from every storage tier.

Cascading revocation

A session key inherits its scopes and bound user from its parent. The resolve_api_key_by_prefix SQL function joins both rows, so a session whose parent is revoked or expired is rejected at the next database lookup.

Redis publishes keyid.invalidate <parent_id> on the acaso:key:invalidated channel; cached entries TTL-expire within roughly 60 seconds across replicas.

Warning: Revoking the parent kills every live session derived from it. This is the right reason to give each human their own key rather than sharing one across a team.

Strict CORS

Every AcasoOS instance must list the console's origin in ACASO_CONSOLE_ORIGINS. Wildcard * is rejected at app startup with a clear configuration error. The CORS layer applies uniformly to every endpoint, including /v1/auth/session.

See Reference: Environment variables for defaults.

What the console does not use

  • localStorage for session tokens. sessionStorage is cleared on tab close, bounding the persistent attack surface.
  • Cookies for credentials. All auth flows as Authorization: Bearer headers — no CSRF surface.
  • Refresh tokens in v1. You re-paste your long-lived key once per workday. A future spec may add refresh tokens behind an HttpOnly cookie.

Defense in depth

Console-side scope-gating (sidebar visibility, button enablement) is ergonomic only. The authoritative authorization decisions live in the database via row-level security and scope checks. A penetration test that bypassed the console gates would still meet the same enforcement at the API layer.

PreviousGetting startedConnectingNext ConceptsTenants