Dyrecteddyrected
Core Concepts

Authentication Model

How `auth: true` turns a collection into an authentication provider — endpoints, tokens, injected fields, and the stateless JWT model.

Authentication in Dyrected is opt-in and collection-scoped. Adding auth: true to any collection turns it into an auth provider. You can have multiple auth collections in one project — for example a users collection for your app's members and a separate __admins collection for the CMS dashboard.


Enabling auth

export const Users = defineCollection({
  slug: "users",
  auth: true,
  fields: [
    { name: "name", label: "Name", type: "text" },
    { name: "role", label: "Role", type: "select", options: ["member", "editor", "admin"] },
  ],
});

auth: true does two things:

  1. Injects fieldsemail (required, unique) and password (hashed, never returned in API responses) are automatically added to every document.
  2. Generates endpoints — a full set of auth routes is mounted under /api/{slug}/.

You do not define email or password in your fields array — they are always present.


Generated endpoints

Every auth collection gets these endpoints automatically:

EndpointMethodPurpose
/{slug}/loginPOSTExchange email + password for a JWT
/{slug}/logoutPOSTReturns a success message (token must be discarded client-side)
/{slug}/meGETReturn the authenticated user's document
/{slug}/refresh-tokenPOSTIssue a new JWT from a valid existing one
/{slug}/forgot-passwordPOSTSend a password reset email
/{slug}/reset-passwordPOSTSet a new password using a reset token
/{slug}/invitePOSTSend an invitation email (authenticated, admin use)
/{slug}/accept-invitePOSTAccept an invite and set a password
/{slug}/first-userPOSTCreate the first user when the collection is empty
/{slug}/initGETReturns { initialized: boolean } — whether any users exist

Stateless JWT

Dyrected auth is stateless — there are no server-side sessions. On login, the server signs a JWT with a secret from your environment and returns it. Every subsequent request must include it:

Authorization: Bearer <token>

The server verifies the token on every request. There is nothing to invalidate server-side on logout — the client discards the token and it expires naturally. The logout endpoint exists to signal this clearly:

{ "success": true, "message": "Logged out. Discard your token." }

What's in the token

The JWT payload contains the user's id, email, roles, and the collection slug it was issued against. All of these are available as user in access functions and hooks:

access: {
  update: ({ user }) => user?.role === 'admin',
}

The password field is never included in the token or in any API read response.


The roles field

If your auth collection has a field named roles, its value is included in the JWT and available as user.roles in access functions. Dyrected does not enforce any role structure — roles can be a select, a text, or any shape you choose. The access function is where you interpret it.

// roles as a select (single role per user)
{ name: 'role', type: 'select', options: ['member', 'editor', 'admin'] }

// Access function reads user.role
delete: ({ user }) => user?.role === 'admin'

Multiple auth collections

You can have more than one auth collection. A common pattern is to keep __admins separate from your application's users:

export const Admins = defineCollection({
  slug: "__admins",
  auth: true,
  fields: [{ name: "name", label: "Name", type: "text" }],
});

export const Users = defineCollection({
  slug: "users",
  auth: true,
  fields: [
    { name: "name", label: "Name", type: "text" },
    { name: "plan", label: "Plan", type: "select", options: ["free", "pro"] },
  ],
});

The Admin UI checks for an authenticated __admins user first, then falls back to any auth collection. Application users logging into your frontend are completely independent of who can log into the CMS dashboard.

See Separating Admin Auth for the full walkthrough.


Password reset flow

The forgot-password endpoint accepts an optional resetUrl body parameter. When provided, Dyrected generates a clickable link in the reset email instead of just the raw token:

// POST /{slug}/forgot-password
{
  "email": "[email protected]",
  "resetUrl": "https://mysite.com/reset-password"
  // → link in email becomes https://mysite.com/reset-password?token=<token>
}

If resetUrl is omitted, the email template receives only the raw token. See Sending Email for customising the reset email template.


Next steps

On this page