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
| Method | Path | Description |
|---|---|---|
POST | /api/auth/register | Create a new user account |
POST | /api/auth/login | Login, receive token / session cookie |
POST | /api/auth/logout | Invalidate session |
GET | /api/auth/me | Get the currently authenticated user |
Register
POST /api/auth/register
Content-Type: application/json
{ "email": "user@example.com", "password": "secret123" }
{
"data": {
"user": {
"id": 1,
"email": "user@example.com"
},
"token": "eyJ..."
}
}
Login
POST /api/auth/login
Content-Type: application/json
{ "email": "user@example.com", "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
| Value | Meaning |
|---|---|
true | Public — no authentication required |
false | Blocked for everyone |
["admin", "editor"] | Allowed for these roles only |
async (ctx) => boolean | Dynamic — 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
| Option | Type | Default | Description |
|---|---|---|---|
secret | string | — | Required. Min 32 characters. |
expiresIn | string | number | "7d" | Token lifetime. String ("7d") or seconds. |
algorithm | "HS256" | "HS512" | "HS256" | Signing algorithm. |
issuer | string | — | JWT iss claim. |
audience | string | — | JWT aud claim. |
auth.session
| Option | Type | Default | Description |
|---|---|---|---|
store | "memory" | SessionStore | "memory" | Session storage backend. Pass a SessionStore instance for custom backends (Redis, database, etc.). |
maxAge | number | 86400 | Session lifetime in seconds. |
checkPeriod | number | 3600 | Expired session cleanup interval in seconds. |
prefix | string | "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
| Option | Type | Default | Description |
|---|---|---|---|
iterations | number | 100000 | PBKDF2 iterations. |
keyLength | number | 64 | Derived key length in bytes. |
minLength | number | 8 | Minimum password length. |
auth.userSchema
| Option | Type | Default | Description |
|---|---|---|---|
name | string | "user" | Schema name for the user model. |
email | string | "email" | Field name used as the login identifier. |
auth.authSchemaName
| Option | Type | Default | Description |
|---|---|---|---|
authSchemaName | string | "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
},
}
| Option | Type | Default | Description |
|---|---|---|---|
login | string | "/auth/login" | Login endpoint path. |
register | string | "/auth/register" | Register endpoint path. |
logout | string | "/auth/logout" | Logout endpoint path. |
me | string | "/auth/me" | Current user endpoint path. |
disableRegister | boolean | false | Block all new user registrations. |