Authentication
How it works
Section titled “How it works”Kide includes a deliberately small, first-party authentication system for the admin:
- Passwords are salted and hashed with PBKDF2 using the Web Crypto API.
- Random session tokens are sent only in an HttpOnly cookie. The database stores a SHA-256 reference, not the reusable token.
- Cookies use
SameSite=Strictand useSecurein production. - Sessions have an absolute 30-day expiry and are validated server-side on every request.
- The current user and role are loaded from the user record on every request, so deleting a user or changing their role takes effect immediately.
- Failed logins are throttled by both client IP and email using database-backed counters.
- State-changing admin requests require a positive same-origin check.
There is no public sign-up route. The first administrator is created through setup or the CLI; later accounts are created by an administrator through invitations.
Supported scope
Section titled “Supported scope”Kide owns the authentication needed for ordinary CMS administration:
- initial administrator setup
- email and password login
- server-side sessions and logout
- administrator-created invitations
- forgot-password recovery when email is configured
- local users and CMS roles
Local email and password login is the only working provider. SSO, MFA, and passkeys are not supported; they would come as an integration with an external identity provider, not as features of the built-in login.
On first run with no users, visiting /admin redirects to /admin/setup where you create the initial admin account.
pnpm cms:admin creates an admin from the terminal, prompting for name, email, and password. It also works for adding admins without an invite.
Inviting users
Section titled “Inviting users”After the initial admin account is created, new users are added through the invite flow:
- Admin creates a user at
/admin/users/newwith an email and role. - System generates a one-time invite token with a 7-day expiry.
- Invite is delivered:
- If the
RESEND_API_KEYenv var is set, an invite email is sent automatically via Resend. - If not set, a copyable invite link is shown in the admin UI for the admin to share manually.
- If the
- New user opens
/admin/invite?token=xxxand sets their name and password. - Token is atomically consumed and cannot be reused, including by two concurrent requests.
Only a hash of the invite token is stored in the database. Invite links expire after seven days.
Password recovery
Section titled “Password recovery”When email delivery is configured, users can request a reset from /admin/forgot-password. The response does not reveal whether the address exists. Reset tokens expire after one hour, are stored only as hashes, and are atomically single-use.
Password recovery is enabled by default. Disable it when the deployment has no email adapter or recovery is handled operationally:
export default defineConfig({ admin: { auth: { password: { forgotPassword: false, }, }, }, collections: [users],});Environment variables
Section titled “Environment variables”Invite and password-reset emails need RESEND_API_KEY (and optionally RESEND_FROM_EMAIL). In production, set CMS_TRUSTED_ORIGIN to the site’s public origin. See Deploy.
Configuration
Section titled “Configuration”admin.auth.password takes enabled (default true), forgotPassword (default true), and emailVerification (default false, unused by the local provider). The failed-login limit is admin.rateLimit; see Rate limiting.
Auth collection
Section titled “Auth collection”The built-in users collection is marked with auth: true:
defineCollection({ slug: "users", labels: { singular: "User", plural: "Users" }, auth: true, fields: { email: fields.email({ required: true, unique: true }), name: fields.text({ required: true }), role: fields.select({ options: ["admin", "editor"], defaultValue: "editor" }), password: fields.text({ admin: { hidden: true } }), },});An auth collection receives stricter default access rules than an ordinary content collection. The password field is automatically hashed on create or update and is omitted from reads, version snapshots, and session user data. Use admin: { hidden: true } to keep it out of the generic edit form.
Middleware
Section titled “Middleware”The middleware in src/cms/middleware/auth.ts (injected by the integration with order: "pre") protects admin pages and CMS API routes, applies security headers, and performs the same-origin check for state-changing browser requests. Public site routes are unaffected. Machine endpoints such as cron and webhooks authenticate separately.
Roles are plain strings stored on the user document. Access rules defined in each collection config use them to gate operations. There’s no built-in role hierarchy. You define what each role can do. See Access Control for details.
Exact token lifetimes, hashing parameters, and rate limits are in Security.