Ultimate

Auth

The output of authentication is an Actor. Nothing downstream authorizes on a session row, a user row or an api key: HTTP, actions, jobs and MCP all read ctx.actor and hand it to the one policy system (Policies and authz). Package @ultimat3/auth (tier 2) — the full reference is packages/auth/README.md; the walk-through is Tutorial 3.

import { defineAuth, login, oauthLogin, postgresAuthAdapter } from '@ultimat3/auth';

export const auth = defineAuth({
  adapter: postgresAuthAdapter(),            // or memoryAuthAdapter()
  session: { absoluteTtlMs: 30 * 864e5, idleTtlMs: 7 * 864e5 },
  password: { minLength: 12 },
  mfa: { issuer: 'Acme' },
  providers: ['github', 'google'],         // required to serve any OAuth route — the default is []
});

const { actor, token, cookie } = await login(auth, { email, password, ip });
const { start, callback } = oauthLogin(auth);   // /auth/sso/:provider and its callback

The tables (x_users, x_sessions, x_accounts, x_verifications, x_api_keys, and the passkeys' x_passkeys, x_passkey_challenges, x_passkey_evidence) are created by every boot; an app writes no migration for them.

Signing in by handle (As of 2026-10): an app whose users are @ada names the lookup once — defineAuth({ adapter, handles: async (handle) => (await db.users.where({ handle }).one())?.id ?? null }) — and calls login(auth, { handle, password }). The handle arrives trimmed and lowercased (normaliseHandle), its failures count against a bucket of its own, and an unknown handle is the same X_UNAUTHENTICATED after the same hash as a wrong password. The app's users row and the auth user share one id: register(auth, { id, email, password }) creates the auth user under it. A handle login on an app that declared no handles is X_CONFIG_INVALID. The deployed demo (dummy/social-media-clone/apps/web/app/auth/) signs in this way.

Sessions

PropertyHow
opaque tokensonly sha256(secret) reaches the database
two expiries, independentabsolute and idle; activity never moves the absolute ceiling (X_SESSION_EXPIRED names which)
not a write per requestlastSeenAt moves at most once per idleSlideMs (default idleTtlMs / 20); a changed IP or user agent is written at once
revocation is immediatethe user row is re-read on every request, so a revoked role takes effect on the next one
the cookie__Host-x_session, HttpOnly, Secure, SameSite=Lax, Path=/, Max-Age
brute forceper-IP, per-account and per-org buckets (X_ACCOUNT_LOCKED); every login failure is one indistinguishable X_UNAUTHENTICATED. rateLimit: { scope: 'shared' } makes the count one for the fleet. The org bucket (orgMaxAttempts, default 100 per window) can be exhausted by someone who knows about 20 of an org's addresses, locking its password sign-in — kept by decision (SECURITY.md)

Signing out is logout(auth, token) for the row plus signOutHeaders() on the response: it expires the session cookie and sends Clear-Site-Data: "cache", "storage" (SIGN_OUT_CLEAR_SITE_DATA), so the browser drops the previous principal's IndexedDB, storage, service worker and cached pages. Revocation at scale is revokeUserSessions, revokeOrgSessions, revokeSessionsCreatedBefore (after rotating a secret) and disableUser, which also revokes the user's live API keys. A passwordHash change through updatePrivileges ends every other session of that user — all of them when no session of theirs is passed, as in a reset.

A sign-in page is one command, As of 2026-11: x g sign-in --password (a native form over login(), its second leg over completeMfa()) or x g sign-in --oidc <provider> (an OpenID Connect issuer, discovered on the first sign-in). Both write the sign-in page, the destroySession action, auth.signInPath, and a boot guard: outside development and test a web or sync role with its sign-in keys unset refuses to start (X_AUTH_SIGN_IN_UNCONFIGURED). --password also writes createSession / confirmSession and the second-factor page; --oidc writes the start and callback routes and drops x new's development viewer while its files are still x new's own. What each writes: CLI reference.

OAuth

github and google ship; registerOAuthProvider adds one. PKCE is mandatory on every provider — usesPkce: false does not typecheck — and the handshake is a signed __Host- cookie bound to its provider. An id token is verified against the provider's JWKS. An identity links to an existing account only when both the provider and that account have verified the address (link: 'verified-email', the default); 'never' is the only alternative. Credentials are <PROVIDER>_CLIENT_ID / <PROVIDER>_CLIENT_SECRET.

Rule (As of 2026-10)
redirect_uri originoauthLogin(auth, { baseUrl }), else APP_URL. With neither the start leg answers X_ENV_MISSING — never the request's Host
a refused leg's bodycode, title, docs, one fixed cause, fix: x errors explain <CODE> --json. The authored cause and fix are the auth.oauth.refused log line
discoverOAuthProviderrefuses a document whose issuer is not the one asked for (a trailing slash aside)
a public client (As of 2026-10-18)tokenEndpointAuth: 'none' on the provider, discoverOAuthProvider or credentials: no <PROVIDER>_CLIENT_SECRET is read and no client_secret is sent; PKCE still is. 'client_secret_basic' sends the pair in Authorization: Basic. Absent: 'client_secret_post', unchanged
applenot a built-in since 26.0.0 (#709): Apple POSTs its callback and both callback routes are GET, so it could never complete a sign-in. An app that needs it registers its own with registerOAuthProvider

SSO: OIDC and SAML behind one IdentityProvider port

Rule: every federated sign-in is an IdentityProvider (@ultimat3/auth), and signInWithIdentity(auth, identity) is the one path from it to a session — the same account-link, MFA and grant rules as OAuth. As of 2026-11-09.

MemberShape
begin({ callbackUrl, clock }){ location, state }
answers(state, params)whether params answer THIS browser's state — RelayState for SAML, state for OIDC — in constant time, nothing verified beyond it. The routes clear the state cookie only on an answer bound to it, so a junk cross-site form cannot end a sign-in in flight
complete({ state, params, callbackUrl, clock, replay }){ profile, expiresAt, claims }
adapterssamlIdentityProvider(config), oidcIdentityProvider({ provider })
your ownbehavesLike(identityProviderConformance, () => harness) from @ultimat3/testing/conformance

identityProviderRoutes(auth, { providers }) mounts both kinds:

RouteMethodPurpose
/auth/sso/:providerGETbegin, seal the state in __Host-x_sso_<id>, 302 to the IdP
/auth/sso/:provider/callbackPOSTthe SAML ACS — register this URL at the IdP. The state cookie is SameSite=None: the IdP posts cross-site
/auth/sso/:provider/callbackGETthe OIDC redirect_uri
import type { Auth } from '@ultimat3/auth';
import {
  identityProviderRoutes,
  oidcIdentityProvider,
  parseSamlMetadata,
  samlIdentityProvider,
} from '@ultimat3/auth';

declare const auth: Auth;
declare const corpMetadataXml: string; // the IdP's metadata document, read at boot

export const sso = identityProviderRoutes(auth, {
  providers: [
    samlIdentityProvider({
      id: 'corp',
      idp: parseSamlMetadata(corpMetadataXml),
      spEntityId: 'https://app.example/saml/metadata',
      trustEmail: true,
      emailDomains: ['corp.example'], // the exact domains this IdP owns
    }),
    oidcIdentityProvider({ provider: 'github' }),
  ],
  successPath: '/app',
});

defineAuth({ providers }) lists both ids; an unknown or not-enabled id is X_IDENTITY_PROVIDER_UNKNOWN (404).

OIDC replay. oidcIdentityProvider claims the handshake's state once in the shared replay store before it spends the code, keyed on when the handshake was issued: a callback lifted from a browser is X_OAUTH_STATE_INVALID even inside the cookie's lifetime, and so is any answer more than 10 minutes after begin (DEFAULT_HANDSHAKE_TTL_MS).

Mount each leg with its meta verbatim — { method: leg.method, path: leg.path, handler: (request) => leg.handle(request.raw), meta: leg.meta }. The ACS (POST) declares csrf: 'cookieless' and authenticate: () => null: the IdP answers with an auto-submitted cross-site form, which the csrf stage refuses on any other route (403 X_CSRF_BLOCKED). CSRF is held there by the protocol instead — the answer must carry this browser's RelayState, checked against the HMAC-sealed state cookie, and its assertion must answer this request's ID.

SAML scope. SP-initiated only: an AuthnRequest over HTTP-Redirect with RelayState, the response over HTTP-POST. Signed assertions are required; encrypted assertions, SHA-1 and RSA under 2,048 bits are refused; trustEmail is required, because SAML has no email_verified, and trustEmail: true requires emailDomains — an IdP's admin can assert any address, so only an address in a listed domain (exact; list each subdomain) is verified, and one outside it can neither link to an existing account nor create one (trustEmail: true with no domains is a type error and X_CONFIG_INVALID at construction); clock skew is ±60 s (skewMs). parseSamlMetadata(xml) → { entityId, ssoUrl, certificates }, refused as X_SAML_METADATA_INVALID with no HTTP-Redirect SSO, no signing certificate, or an RSA key under 2,048 bits. Verification is in-package, no dependency (build vs wrap). Every refusal is X_SAML_RESPONSE_INVALID with meta.check:

Checkmeta.check
a DOCTYPE, an entity, a processing instruction, over 512 KiB, more than 64 namespace declarations, one attribute twicexml
more than one Assertion, one not a direct child, a ds:Object in a Signaturestructure
one ID on two elementsid-duplicate
an EncryptedAssertion or EncryptedIDencrypted
the assertion unsigned (a signed response alone is refused)signature-missing
not RSA-SHA256 / SHA-256 / exc-c14n + envelopedalgorithm
the Reference URI is not the assertion's own IDreference
no metadata certificate verifies (KeyInfo is never read)signature
content altereddigest
Status is not Successstatus
Issuer is not the metadata's entityIDissuer
Destination / Recipient is not the ACS URLdestination / recipient
InResponseTo is not our AuthnRequest's IDin-response-to
Audience is not spEntityIdaudience
NotBefore / NotOnOrAfternot-before / expired
a transient or empty NameIDsubject
RelayState is not the browser'srelay-state
an assertion already consumed (auth.totpReplay, shared)replay

Passkeys

WebAuthn, verified by hand on WebCrypto — no dependency (As of 2026-11). Two uses, one declaration: defineAuth({ passkeys: { rpId, origins } }). ES256 and RS256; CBOR, COSE and DER are parsed in this package, every malformed byte a refusal.

import { canonicalJson } from '@ultimat3/core';
import {
  defineAuth,
  memoryAuthAdapter,
  passkeyLogin,
  passkeyStepUp,
  sha256Hex,
  verifyPasskeyEvidence,
} from '@ultimat3/auth';

const auth = defineAuth({
  adapter: memoryAuthAdapter(),
  passkeys: { rpId: 'example.com', origins: ['https://example.com'] },
});

// Sign-in: register onto a signed-in account, then usernameless sign-in to the same session login() mints.
const { registerOptions, register, loginOptions, login } = passkeyLogin(auth);

// Step-up: the proof is bound to bytes the SERVER derives from the input, and stored as evidence.
const stepUp = passkeyStepUp<{ readonly to: string; readonly minor: number }>(auth, {
  ttlMs: 60_000,
  digest: ({ input }) => sha256Hex(canonicalJson(input)),
});

declare const userId: string;
declare const response: unknown;
const evidence = await stepUp.verify({ userId, input: { to: 'acct-7', minor: 12_500 }, response });
// Later, in another process, with the row alone:
const approved = await verifyPasskeyEvidence(evidence, { rpId: 'example.com', origins: ['https://example.com'] });
Rule
tierfunctions over Auth, wrapped by the app's own actions (tier 2 constructs no action) — like login and completeMfa
browser@ultimat3/auth/client: newPasskey(options) / getPasskey(options) answer { status: 'ok', response }, 'cancelled', 'unsupported' or 'failed' — never a throw. Its graph is two files, ~2.3 kB minified (client-bundle.test.ts); an island never loads the server barrel
checked, every ceremonyclientDataJSON type, the origin against the declared list exactly (no cross-origin iframe), rpIdHash, UP and UV (user verification is required, never preferred), the signature with the STORED key's algorithm
challenge32 random bytes, stored as its sha256 in x_passkey_challenges with a TTL, consumed by one DELETE … RETURNING — single-use across the fleet, never a process map. Unknown, spent and late are one code: X_PASSKEY_CHALLENGE_EXPIRED
countera signature counter that did not move forward is a cloned authenticator (X_PASSKEY_INVALID); both zero is an authenticator that keeps none, allowed. Advanced compare-and-set
registrationattestation: 'none' — the statement is parsed, not trusted; a credential id already registered to anyone is X_AUTH_WRITE_FAILED; a challenge issued to one account never registers onto another
sign-inthe session is mfaSatisfied: a user-verified passkey is possession plus the device's PIN or biometric
step-up challengeSHA-256("ultimate.passkey.step-up.v1\0" ‖ nonce ‖ len ‖ digest ‖ userId) — never supplied by a caller. A proof for other bytes, or none, is X_PASSKEY_STEP_UP_REQUIRED
evidencex_passkey_evidence: the signed bytes, the nonce, the digest and the key itself, so verifyPasskeyEvidence re-checks it with no adapter and no clock; any edit to digest, nonce, user, key or signed bytes is false. auth.adapter.listPasskeyEvidence(digest) finds the approvals of one digest
refusalsX_PASSKEY_INVALID (which check failed is the auth.passkey.refused log line, never the cause), X_PASSKEY_CHALLENGE_EXPIRED, X_PASSKEY_STEP_UP_REQUIRED; a bad passkeys block is X_CONFIG_INVALID at defineAuth

MFA, verification, API keys, workloads

CapabilityCalls
TOTP + recovery codesenrolTotp, saveTotpSecret, generateRecoveryCodes. The secret is sealed at rest under the app's master key and a plaintext one is refused, never read (X_MFA_SECRET_UNSEALED; x auth seal-mfa seals rows from before); a recovery code is consumed in one statement, so it works once. A password proven with a factor outstanding is X_MFA_REQUIRED, whose meta.challenge is a sealed five-minute credential; completeMfa(auth, challenge, code) finishes the sign-in, meters the guess against the same lockout buckets a password guess spends, and refuses a spent code on every replica (x_totp_steps, installed by the boot)
email verification, password reset, any other mailed credentialdefineVerificationPurpose({ name, template, ttlMs, identifier? }) → { issue, consume } (As of 2026-11); emailVerification and passwordReset are its two built-in instances, and issueVerification / consumeVerification take any declared name. A token is consumed only when its hash matches, so a wrong guess cannot burn the victim's live link, and one purpose's token never consumes another's. A name declared twice or with no template is X_VERIFICATION_PURPOSE_INVALID at boot; an undeclared one at the call is X_VERIFICATION_PURPOSE_UNKNOWN (400). VerificationPurpose is string: a switch over it needs a default arm. The mail is sent through an injected MailSender (Mail)
API keys — how an agent authenticatesissueApiKey → ult_<env>_<id>_<secret>, shown once; verifyApiKey + apiKeyActor give a kind: 'agent' actor whose scopes are the key's and never the owner's roles (X_API_KEY_INVALID). A key is its owner's credential: a missing or disabled owner refuses it on the next use, a wildcard scope (*, <resource>:*) is refused at issue, and an owned key keeps only the scopes its owner's grants cover — pass grantsOf to expand roles (packages/auth/README.md); a key with no userId is unchanged. apiKeyResolver(() => store) is both as the resolveToken a bearer mount and defineAppMcp() take: { actor, scopes } for a live key, one null for every wrong one, and a store fault left a throw. In a test the store is memoryAuthAdapter()
service-to-serviceverifyWorkloadToken reads a Kubernetes service-account token, a SPIFFE JWT-SVID or a cloud IMDS token (they are all one JWT); actorFromService gives a kind: 'service' actor. The returned WorkloadToken carries raw, the whole decoded payload, read only after every check held. mTLS is the mesh's job
an external issuer's bearer (As of 2026-11)jwtBearerResolver({ keys, issuers, audience, requiredScope?, accept?, actorOf }) — apiKeyResolver's twin for JWTs, the resolveToken a bearer mount and defineAppMcp() take. keys is a thunk read when a token arrives (no discovery at import); accept(raw) is the app's own claim check after the signature; actorOf(token) builds the actor. Every refusal — signature, issuer, audience, expiry, scope, accept, and a key set that cannot be read — is null plus one auth.jwt_bearer.refused log line naming the check, never a claim's value. The mount answers that null as 401 X_UNAUTHENTICATED

Authorization server

As of 2026-11. authorizationServer({ issuer, store, consentPath, lifetimes, scopes, registration?, grants?, resources? }) is one OAuth 2.1 server for an app's MCP endpoint, public API, CLI and partners alike. It ships the RFC rules; the consent page, who may be a client and what a subject becomes are the app's.

import type { Actor, PgExecutor } from '@ultimat3/core';
import { authorizationServer, oauthBearerResolver, postgresAuthServerStore } from '@ultimat3/auth';

declare const executor: PgExecutor;
declare function actorFor(subject: string): Promise<Actor | null>;

export const oauth = authorizationServer({
  issuer: 'https://www.example.com',
  store: postgresAuthServerStore({ executor }),
  consentPath: '/oauth/consent',               // the app's page; the advertised authorization_endpoint
  lifetimes: { accessSeconds: 600, refreshIdleDays: 30, clientSeconds: 900 },
  scopes: ['cases:read', 'cases:write'],
  grants: ['authorization_code', 'refresh_token', 'client_credentials'],
});
export const runtime = { routes: [...oauth.routes] };           // apps/web/runtime.ts
const resolveToken = oauthBearerResolver(oauth, { actor: (grant) => actorFor(grant.subject) });
PieceWhat it does
routesGET /.well-known/oauth-authorization-server (RFC 8414), POST /oauth/token, /oauth/revoke (RFC 7009), /oauth/introspect (RFC 7662), plus /oauth/register (RFC 7591, registration: 'dynamic') and /oauth/device (RFC 8628). Plain Routes for runtime.routes, public, no cookie authenticates them. Every answer is no-store RFC 6749 JSON
consentthe page calls readRequest(url) → { client, scopes, resource, redirectUri } or a refusal; an unknown client or unregistered redirect has redirectTo: null — render it, never redirect. Its action calls approve({ subject, request, scopes? }) / deny({ request }) for the redirect URL (code, state, iss). A forged request is X_AUTH_CONSENT_INVALID (422)
devicethe verification page calls readDevice({ userCode, key }), approveDevice / denyDevice; key is the caller's bucket — 10 misses lock it (X_ACCOUNT_LOCKED)
registerClient(...)a client the app creates itself — a partner's client_credentials client; the secret is shown once and stored hashed
resolve(token){ subject, scopes, clientId, resource } | null. oauthBearerResolver(server, { actor, resource? }) is the resolveToken a bearer mount and defineAppMcp take; actor runs per call, so a removed member loses access at once
revoke({ subject, clientId? }) · purge({ before })immediate; purge is the app's task to schedule, nothing here runs on a timer
RuleHeld by
redirect URIs match byte for byteauthorization-server-request.test.ts
S256 only; an absent method is plain and refusedsame
a code lives 2 minutes, is stored hashed, is single use; a replay revokes what it mintedauthorization-server.test.ts, authServerStoreConformance from @ultimat3/testing/conformance (racing redemptions, run on memory and on Postgres by auth-server-store-conformance-postgres.live.test.ts)
refresh rotates; a retired one's replay revokes the grant; idle past refreshIdleDays is deadauthorization-server.test.ts
resource (RFC 8707) binds the grant; one not in resources is invalid_targetauthorization-server-request.test.ts, oauth-bearer-resolver.test.ts
tokens are opaque and stored hashed, never a JWT, so revoke holds on the next lookuptoken-endpoints.test.ts
an https:// client_id is a metadata document, fetched through outbound({ dial: 'public' }), 5 KiB, cached 5 minutes, public clients onlyauthorization-server-clients.test.ts
client_credentials: confidential only, no refresh token, subject is the clientgrant-client-credentials.test.ts
dynamic registration never hands out client_credentials and is counted per addressauthorization-server-clients.test.ts

Bad configuration is X_AUTH_SERVER_INVALID naming the field; a grant not in grants answers unsupported_grant_type and is logged X_AUTH_GRANT_DISABLED. The five tables (x_oauth_*) are applied by the boot. For one MCP endpoint, mcpAuthorizationServer (MCP and AI).

Every auth code — X_UNAUTHENTICATED, X_SESSION_EXPIRED, X_MFA_REQUIRED, X_OAUTH_*, X_ACCOUNT_LOCKED, X_API_KEY_INVALID and the rest — is in Error codes.

Edit this page on GitHubThis page as markdown