Skip to content
Version: Current

Identity and access

The estate has two credential planes, and they never convert into each other. People sign in through the identity provider at auth.tcp.systems. Machines call the model gateway with virtual keys. A login is not an API credential, and an API credential is never proof that a human is present.

Pocket ID is the identity provider for the estate today. Every surface (studio, console, chat and the consumer surface) authenticates against it, passkey first, with one client per realm. The issuer is https://auth.tcp.systems; its discovery document and JWKS are public and answered 200 when probed on 2026-11-05. See the auth surface page for the live contract.

In parallel, the tcpsystems/auth repository builds the first-party provider (authd) that is intended to take this role over. Slice 1 was gate-green on 2026-10-30: per-realm OIDC discovery and JWKS, authorization code with mandatory PKCE (S256), WebAuthn passkeys as the primary human factor, TOTP and one-time recovery codes as fallbacks, opaque server-side sessions, and an append-only hash-chained audit log. The normative spec is docs/AUTH-PLATFORM.md in that repository, and the threat model is docs/AUTH-THREAT-MODEL.md. Deep operational detail stays there; this site summarises and links.

One rule is permanent across both providers: no third-party identity SaaS is ever the source of truth. Federation over standard OIDC is a later feature and never becomes canonical.

A surface is an OIDC relying party. It never reads the identity database and never mints identity. The shape of the flow:

  1. The surface redirects the browser to the provider’s authorization endpoint, with state, a nonce, and a PKCE S256 challenge.
  2. The provider authenticates the human with a passkey (TOTP or a recovery code as fallback) and redirects back with a single-use code.
  3. The surface exchanges the code at the token endpoint and verifies the ID token: signature against the published JWKS, exact issuer, its own client id in the audience, expiry, and the nonce it sent.
  4. The surface derives its own session and stores no identity tokens. The console contract (docs/CONSOLE-OIDC-CONTRACT.md in tcpsystems/auth) states this explicitly: no access, refresh or ID token is stored anywhere.

A passkey is a WebAuthn credential bound to the exact origin that created it. The browser refuses to sign an assertion for a different origin, which is what makes the flow phishing resistant. A passkey cannot be replayed, forwarded or typed into a lookalike page.

The consumer surface on acumen is the exception by design: a person signing in there has no key yet, so sign-in is invite-only with a recovery code, and OIDC is deferred on that path.

Machines authenticate to acumen with virtual keys (vk_…), presented as bearer tokens on /v1/*. A key carries scopes, an optional model allowlist, and spend and rate caps. The full contract, including the grant ledger and the failure matrix, is acumen authentication. The identity provider is not involved in this plane at all.

The rules are the same on both planes:

  • A credential is printed once and stored only as a hash. Virtual keys are SHA-256 hashed at rest; invite tokens the same. One-time bootstrap values in authd land in a bootstrap.json file with mode 0600 and are never logged.
  • Configuration references secrets by name, never by value. The console OIDC client is provisioned as OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_REDIRECT_URI and a client secret held as a deploy-time secret; the exact store for the console’s client secret is an operator detail, named on the auth surface page.
  • No secret appears in logs, command lines, URLs or source. That includes this site: documentation names the variable or the path, never the value.
  • Fail closed. Auth answers 401 or 403 by default. A missing client, realm or signing key is a refusal, not an anonymous session. Unknown key id, unverifiable signature or a store error deny the request.
  • Deny by default at the edge. The Pocket ID admin UI is tailnet-only; its /setup path answers 404 from the public internet.
  • PKCE is mandatory and only S256 is sent, even if the provider advertises weaker methods. Redirect URIs match exactly.
  • A weaker factor weakens the session. TOTP and recovery codes carry a visibly weaker authentication-methods reference and narrower scope in the first-party provider; administrative scopes require a passkey.
  • Every security decision is audited. Login, token issuance, revocation, key rotation and refusals append to a tamper-evident chain in the first-party provider.

Surfaces return the failure class: an unknown client, an expired session or a rejected assertion. For the consumer surface the classes are different (an unknown, expired or used invite answers 410, and the page does not say which). Diagnosis steps for each path are in Sign in to the surfaces.

One recorded misdiagnosis is worth knowing: a report of text/html discovery with HTTP 200 was a wrong-host fetch, not a provider fault. Before reporting the provider down, fetch https://auth.tcp.systems/.well-known/openid-configuration directly and check that the answer is JSON whose issuer is exactly https://auth.tcp.systems.