Skip to content

Auth configuration

View as Markdown

Engine options validate against shared zod schemas via resolveAuthOptions (Better Auth secret docs). Secrets stay out of code with {env:NAME} refs — resolved from process.env first, then your resolveSecrets hook for Vault/AWS/GCP:

import { resolveAuthOptions } from "@storyshelf/auth";
const authOptions = await resolveAuthOptions({
db: database,
secret: "{env:SECRET}",
baseURL: process.env.PUBLIC_BASE_URL!,
sso: { providers: [...] },
resolveSecrets: async (names) => vault.read(names),
});
const shelf = createShelfAuth(authOptions);

Precedence: explicit literal > {env} ref > schema default; code recipes > DB rows > plugin defaults at sign-in.

Sessions are Better Auth DB rows under the storyshelf_session cookie (seven-day TTL, cookieCache off — see cookie cache) mounted at /api/auth/* via the Hono integration. Engine identities mirror into users via database hooks. See also database adapters.

Reword the sign-in page without code via ui.auth (or SS_AUTH_* env vars on the dev/fly servers):

const app = createShelfApp({
...,
ui: {
auth: {
title: "Welcome back",
subtitle: "Sign in to review visual changes",
ssoLabelTemplate: "Continue with {label}",
helpText: "Use your company SSO. Need access? Ask in #design-systems.",
},
},
});

Available keys: title, subtitle, passwordLabel, passwordPlaceholder, submitLabel, ssoLabelTemplate (must contain {label}), helpText, footerText. Page structure stays fixed — layout control remains via brand config.

Every logged-in user gets /profile: avatar, editable display name, email, site role, provider, project memberships, and sign-out — plus Devices (per-device revoke, sign out others), Passkeys (list, delete, register), and password change for local accounts. A user-edited display name survives IdP refresh (display_name_override).

See also Auth concepts.