Skip to main content

Authentication

The studio has no identity of its own. It mounts the main app's Better Auth handler at its own /api/auth/*, reads the same session cookie, and mints a short-lived service bearer from the same secret for every call it makes to apiome-rest and Spire. A sign-in on main.apiome.dev is therefore a sign-in on suite.apiome.dev — as long as the two deployments agree on three values.

The pairing rules​

VariableRule
BETTER_AUTH_SECRETByte-identical on the main app, the studio and Spire
BETTER_AUTH_COOKIE_DOMAINIdentical on the main app and the studio: the parent domain both origins sit under (.apiome.dev). Production only — localhost cannot carry a parent-domain cookie
BETTER_AUTH_URLPer origin: each deployment's own public URL

Breaking the first two is silent — the user simply gets a second login — so the studio's boot checks log a startup warning naming the variable (missing-secret, host-only-cookie, origin-outside-cookie-domain). The cookie is __Secure-better-auth.session_token (Domain=<cookie domain>, SameSite=Lax, Secure, HttpOnly); sessions live in the database for 30 days, refresh every 24 hours, and are cached in a signed cookie for 60 seconds.

Where sign-in happens​

Every guarded page sends a signed-out visitor to /login:

  • When NEXT_PUBLIC_MAIN_APP_URL is another origin — the production layout — the studio's /login redirects to <main app>/login?callbackUrl=…. The main app's page has every sign-in provider the installation configured, and the session cookie it sets is readable on the studio's origin. callbackUrl is validated before it is followed.
  • When the main app is the same origin, the studio shows its own form: Sign in to Apiome Studio — Use your Apiome account credentials. It offers email and password plus the OAuth providers in the environment (GITHUB_*, GITLAB_*), and admin-screen provider configuration when INTERNAL_SERVICE_TOKEN is set. An account with two-factor authentication enrolled is told to sign in on the main app.

NEXT_PUBLIC_MAIN_APP_URL must never be the studio's own origin: the redirect would land on the studio's own guarded /login and loop.

The user-facing version of this is Sign in.

The service bearer​

For every server-side call the studio signs a JWT (HS256, one hour) with BETTER_AUTH_SECRET and sends it to apiome-rest and Spire. The contract is docs/contracts/suite-auth-token.contract.json (v2):

ClaimValue
sub / user_idThe user's id (UUID)
email, nameFrom the session
current_tenant_idThe tenant the session is scoped to
two_factor_elevatedWhether the account has two-factor authentication enrolled

Spire verifies against BETTER_AUTH_SECRET, then JWT_SECRET. With no secret configured the studio sends requests unauthenticated and they are refused with 401; the unsigned-bearer boot warning says so.

Rotating the secret​

BETTER_AUTH_SECRETS rotates the session cookie only. The bearer's secret must change on the studio and Spire together: keep the old value under JWT_SECRET on Spire during the window, roll both, then drop it.

Cutover and rollback​

The move from NextAuth to Better Auth has no feature flag. apiome-ui at or after 651957d9 pairs with designer 0.63.0 and Spire 0.21.0 or later; rolling back is a paired redeploy, and the Suite holds no authentication state of its own. The retired NEXTAUTH_URL, NEXTAUTH_COOKIE_DOMAIN and NEXTAUTH_SECRET earn a startup warning on the studio — and stop Spire from starting.

Development accounts​

The seeded development stack signs in as ada@example.com (password apiome-dev); grace@example.com is a second seeded member.