Auth

Authentication is built into ApiPlugin. Define an auth block in your config to enable it — omit it to disable completely.


Setup

const roles = ["admin", "editor", "user"] as const;
type Role = (typeof roles)[number];

new ApiPlugin<Role>({
	auth: {
		roles,
		defaultRole: "user",

		jwt: {
			secret: "your-secret-key-at-least-32-characters",
			expiresIn: "7d", // "1h" | "30m" | "7d" | seconds as number
			algorithm: "HS256", // "HS256" | "HS512" — default: "HS256"
		} satisfies JwtConfig,

		session: {
			store: "memory",   // "memory" (default) or a custom SessionStore instance
			maxAge: 86400,     // seconds — default: 86400 (24 hours)
			checkPeriod: 3600, // expired session cleanup interval in seconds — default: 3600
			prefix: "datrix:session:",   // session key prefix for memory store — default: "datrix:session:"
		} satisfies SessionConfig,

		defaultPermission: {
			create: ["admin"],
			read: true,
			update: ["admin", "editor"],
			delete: ["admin"],
		},
	} satisfies AuthConfig<Role>,
});

JWT and session can be enabled simultaneously — responses include both a token and a session cookie.

A user schema with an email field must exist before enabling auth. The plugin creates an authentication table alongside it automatically.


Endpoints

MethodPathDescription
POST/api/auth/registerCreate a new user account
POST/api/auth/loginLogin, receive token / session cookie
POST/api/auth/logoutInvalidate session
GET/api/auth/meGet the currently authenticated user

Register

POST /api/auth/register
Content-Type: application/json

{ "email": "[email protected]", "password": "secret123" }
{
  "data": {
    "user": {
      "id": 1,
      "email": "[email protected]"
    },
    "token": "eyJ..."
  }
}

Login

POST /api/auth/login
Content-Type: application/json

{ "email": "[email protected]", "password": "secret123" }

When session is enabled, the response also sets a sessionId cookie (HttpOnly).

Authenticated requests

Pass the JWT token in the Authorization header:

GET /api/products
Authorization: Bearer eyJ...

Or send the session cookie — the browser handles this automatically.


Permissions

Permissions are defined per schema. They control who can call each CRUD action.

defineSchema({
  name: "product",
  fields: { ... },
  permission: {
    create: ["admin", "editor"],  // role array
    read: true,                    // public
    update: ["admin", "editor"],
    delete: ["admin"],
  } satisfies SchemaPermission<Role>,
} satisfies SchemaDefinition)

Permission values

ValueMeaning
truePublic — no authentication required
falseBlocked for everyone
["admin", "editor"]Allowed for these roles only
async (ctx) => booleanDynamic — evaluated per request

Dynamic permissions

permission: {
  update: [
    "admin",
    async (ctx) => {
      const record = await ctx.datrix.findById("post", ctx.id!)
      return record?.authorId === ctx.user?.id
    },
  ] satisfies PermissionValue<Role>,
}

Field-level permissions

Restrict read or write access to individual fields:

fields: {
  email: {
    type: "string",
    permission: {
      read: ["admin", "editor"],   // hidden from other roles
    } satisfies FieldPermission<Role>,
  },
  salary: {
    type: "number",
    permission: {
      write: ["admin"],            // only admin can set this
    } satisfies FieldPermission<Role>,
  },
}

defaultPermission

Applied to schemas that have no permission defined:

auth: {
  defaultPermission: {
    create: ["admin"],
    read: true,
    update: ["admin"],
    delete: ["admin"],
  },
}

Configuration reference

auth.jwt

OptionTypeDefaultDescription
secretstringRequired. Min 32 characters.
expiresInstring | number"7d"Token lifetime. String ("7d") or seconds.
algorithm"HS256" | "HS512""HS256"Signing algorithm.
issuerstringJWT iss claim.
audiencestringJWT aud claim.

auth.session

OptionTypeDefaultDescription
store"memory" | SessionStore"memory"Session storage backend. Pass a SessionStore instance for custom backends (Redis, database, etc.).
maxAgenumber86400Session lifetime in seconds.
checkPeriodnumber3600Expired session cleanup interval in seconds.
prefixstring"datrix:session:"Session key prefix — only used with the default memory store.

To use a custom store, implement the SessionStore interface exported from @datrix/api:

import type { SessionStore, SessionData } from "@datrix/api"

class RedisSessionStore implements SessionStore {
  async get(id: string): Promise<SessionData | undefined> { ... }
  async set(id: string, data: SessionData): Promise<void> { ... }
  async delete(id: string): Promise<void> { ... }
  async cleanup(): Promise<number> { ... }
  async clear(): Promise<void> { ... }
}

// Then pass it to session config:
session: {
  store: new RedisSessionStore(redisClient),
}

auth.password

OptionTypeDefaultDescription
iterationsnumber100000PBKDF2 iterations.
keyLengthnumber64Derived key length in bytes.
minLengthnumber8Minimum password length.

auth.userSchema

OptionTypeDefaultDescription
namestring"user"Schema name for the user model.
emailstring"email"Field name used as the login identifier.

auth.authSchemaName

OptionTypeDefaultDescription
authSchemaNamestring"authentication"Name of the internal table that stores hashed passwords and session data alongside the user schema.

auth.endpoints

Override the default auth route paths or disable registration entirely:

auth: {
  endpoints: {
    login:           "/auth/login",    // default
    register:        "/auth/register", // default
    logout:          "/auth/logout",   // default
    me:              "/auth/me",       // default
    disableRegister: false,            // set true to block new registrations
  },
}
OptionTypeDefaultDescription
loginstring"/auth/login"Login endpoint path.
registerstring"/auth/register"Register endpoint path.
logoutstring"/auth/logout"Logout endpoint path.
mestring"/auth/me"Current user endpoint path.
disableRegisterbooleanfalseBlock all new user registrations.