Auth configuration
Config-as-code and secrets
Section titled “Config-as-code and secrets”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.
Custom login text
Section titled “Custom login text”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.
Profile
Section titled “Profile”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.