Skip to content

Authentication

  • Passwords hashed with PBKDF2 (Web Crypto API)
  • Session tokens stored in cms_sessions table
  • HttpOnly cookies with SameSite=Lax (+ Secure in production)
  • 30-day session expiry with server-side validation

On first run with no users, visiting /admin redirects to /admin/setup where you create the initial admin account.

Any collection with auth: true enables login:

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", "viewer"] }),
password: fields.text({ required: true, admin: { hidden: true } }),
},
});

The password field is automatically hashed on create/update. Use admin: { hidden: true } to hide it from the edit form.

The middleware in src/middleware.ts protects all /admin and /api/cms routes. Public pages are unaffected.

Roles are plain strings stored on the user document. Access control rules in src/cms/access.ts use them to gate operations. There’s no built-in role hierarchy — you define what each role can do.