Skip to content

Authentication

Kenresoft CMS uses Better Auth for authentication, mounted at /api/v1/auth/*. There is one identity system: CMS staff, website visitors and Commerce customers are all rows in the same user, account and session tables.

Core user (Better Auth: user, account, session, verification)
├── role: owner | admin | editor | author | viewer → CMS access
├── role: none (default) → website / application account
└── plugin profile (optional), e.g. Commerce customer profile
  • New accounts default to role none. A none account cannot reach the admin API, on any route.
  • A CMS role is only ever assigned by trusted server-side code: the Owner bootstrap, Add user, and the role and ownership routes. Sign-up and profile updates cannot set it.
  • A disabled account is treated as signed out everywhere, and its sessions are revoked.
  • Role capabilities are in Users & Roles.

A normal website or application account therefore never receives CMS administrative access, however it registers.

Authentication is cookie-session based. There is no JWT or bearer-token mode configured for API clients. The session cookie is set by the API with SameSite=None and Secure always on, because the admin and API are on different origins by default. Consequences:

  • Browsers that block third-party cookies drop it. A website with accounts should use the same-origin proxy, which makes the cookie first-party to the website.
  • Any origin that sends credentialed requests must be listed in CORS_ORIGINS.

Every email/password account must verify its email address before it can sign in. Signing in with an unverified address fails with EMAIL_NOT_VERIFIED and a fresh link is sent. The one exception is the bootstrap Owner. If email is not configured, verification links are written to the Worker logs.

For staff accounts the link opens the admin’s /verify-email page. For a website account the link is Better Auth’s verification URL, which redirects to the callbackUrl your site passed at sign-up (an origin in CORS_ORIGINS).

  • POST /api/v1/public/password-reset/request always returns the same generic message, whether or not an account exists. The token is random, single-use, expires after one hour and is stored only as a hash. redirectUrl is honored only for website accounts and only for an origin in CORS_ORIGINS.
  • POST /api/v1/public/password-reset/confirm sets the new password.
  • POST /api/v1/auth/change-password needs a session, and signs out other devices by default.

TOTP with backup codes, enabled per account (on the admin Profile page, or through cms.auth.twoFactor on a website). There is no SMS or email one-time-code method. A sign-in for a two-factor account returns twoFactorRequired and creates no session until the code is verified.

Account recovery has three layers, from least to most privileged. See Account and owner recovery.

Auth endpoints are listed in Astro authentication and the API routes reference.