Skip to content

Authentication

The platform supports three authentication paths today. Each has a distinct lifetime, threat model, and audience.

1. Google OIDC (browser users)

User-facing OIDC flow, present on the platform Host only (not on user-Device installs).

Source: projects/matrix-3/packages/system-auth/src/google-oidc.ts.

Flow

  1. User clicks "Sign in with Google" on /apps/web/.
  2. auth.google.login returns a redirect URL with PKCE state.
  3. Browser navigates to https://accounts.google.com/o/oauth2/v2/auth?....
  4. After approval, Google redirects to /api/auth/callback/google?code=...&state=....
  5. auth.google.callback exchanges code for tokens, verifies the ID token signature against Google's JWKS, extracts (iss, sub, email, name).
  6. principalStore.findOrCreate(iss, sub, ...) returns the principal.
  7. sessionService.createSession(principalId, ...) mints an mx_session JWT.
  8. Set-Cookie: mx_session=...; HttpOnly; Path=/; SameSite=Lax.
  9. Browser is redirected to the original redirect from OAuth state.

Properties

  • Token type: HMAC-SHA256-signed JWT. Source: HostSessionService (host-auth.ts:196-285).
  • Lifetime: SESSION_DURATION_MS = 7 * 24 * 60 * 60 * 1000 — 7 days.
  • Storage: HttpOnly cookie. Never accessible to JavaScript.
  • Verification: signature + issuer + expiry + revocation list.
  • Revocation: by jti (JWT id) into HostAuthStateStore.revokedSessions[].

Anonymous

If no mx_session cookie is present, auth.identity.resolve returns { authenticated: false }. The browser can still call public endpoints (/healthz, /api/apps).

Each paired Device carries a heartbeat token. Source: rotated by HostLinkStore.rotateHeartbeatToken at exchange and refresh.

Flow

  1. Pair flow exchange returns heartbeatToken to the Device.
  2. The Device persists the token (in its own <host-home>/credentials/).
  3. The Device's host.control POSTs to /_auth/host-link/heartbeat with Authorization: Bearer <token> plus a JSON body identifying the link.
  4. The gateway calls auth.hostLink.verifyHeartbeat.
  5. The op compares an HMAC over the token to link.heartbeatTokenHash. Constant-time compare.
  6. If valid, lastRefreshedAt is updated and the response acknowledges.

Properties

  • Token type: opaque random bytes, hashed at rest.
  • Lifetime: rotates on every credential refresh; explicitly rotates on demand.
  • Storage: plaintext on the Device only; hashed on the platform.
  • Verification: HMAC compare.
  • Revocation: any operation that flips link.status = 'revoked' invalidates the hash.

3. Local-client (loopback)

When a request arrives over loopback (127.0.0.1 / ::1 / localhost), the gateway treats the connection as local-client.

Source: projects/matrix-3/packages/system-auth/src/index.ts:1311-1320.

ts
if (loopback) {
  return {
    claims: null,
    authenticated: true,
    principalId: 'host-service',
    displayName: 'Matrix Host Service',
    localClient: true,
  };
}

The local-client identity bypasses session checks. This is how a CLI on the same host invokes auth.principal.list without logging in. It is not what a remote user gets, even if the platform serves on a non-loopback interface — the loopback signal must come from the connection itself.

This is critical: HiveCast must run behind a TLS-terminating proxy (Caddy) that does NOT spoof loopback. If a proxy passes connections through with 127.0.0.1 as the source, the platform mistakenly treats them as local-client. The deploy-cloud Caddy config does not do this; review any new proxy configuration carefully.

NATS user JWT (per-Device, NOT a browser auth path)

In addition to the three above, paired Devices carry a NATS user JWT used for bus traffic only. This is not a session token — it does not authenticate HTTP requests. It authenticates NATS connections, with Ed25519 NKey challenge-response.

The NATS user JWT and the Host-link bearer token serve different layers and are issued by the same exchange but used independently.

Cross-checks

CheckWhat it doesSource
issSession JWT issued by this HostHostSessionService.validateSession
expNot expiredsame
RevocationNot in revokedSessions[]same
HMACSignature validsame
Cookie HttpOnlyJS can't readgateway sets the flag
SameSite=LaxMitigates CSRFgateway sets the flag
Origin(target) Origin header validation on POSTnot yet enforced platform-wide

What is NOT yet implemented

  • GitHub OIDC. Schema accepts arbitrary (issuer, subject) pairs but only Google's flow exists.
  • Email + passkey. Mentioned in ARCHITECTURE-PLATFORM-TOPOLOGY.md §3, not implemented.
  • Enterprise SSO (Okta/Entra/Duo). Schema-ready (workspaceRealm); flow not built.
  • Multi-factor (TOTP/WebAuthn) at HiveCast level. The provider's MFA (Google's) is the only factor today.
  • Origin header validation on writes. CSRF mitigation today is SameSite=Lax cookies + the fact that NATS bus operations use the Bus token, not the session cookie.

Session secret rotation

HostSessionService requires auth.sessionSecret ≥ 32 chars. To rotate:

  1. Generate a new secret: HostSessionService.generateSecret() returns a 32-byte hex string.
  2. Update host.json with the new secret.
  3. Restart the platform Host.
  4. All existing sessions become invalid. Users must re-login.

There is no zero-downtime rotation today. Target state.

Live proof commitments

WORKSTREAMS/product-launch/HIVECAST-DEVICE-ENROLLMENT-SPEC.md records live proofs:

  • Device-scoped credential issuance: richard-device-scoped-proof-20260501-060747.
  • Host-link revoke + NATS user JWT revocation: richard-device-revoke-proof-20260501-062524.
  • UI revoke: richard-ui-revoke-proof-20260501-064035.
  • Credential refresh: richard-refresh-proof-20260501-065817.

These are the canonical accept-tests for the auth flows on hivecast.ai.

See also

Source: projects/matrix-3/packages/system-auth/src/index.ts:435-448 for auth.identity.resolve. host-auth.ts:196-285 for HostSessionService. google-oidc.ts for the OIDC flow.