Concepts

RBAC — Roles and permissions

Per-application role-based access control in auth-service. Roles, permissions, and how they appear in JWT access tokens.

Auth Service implements per-application RBAC. Each application has its own isolated set of roles and permissions. A user can hold different roles in different applications.

Model

Application
  └── Role (e.g. "admin", "editor", "viewer")
        └── Permission (resource + action, e.g. "documents.write")

User ── UserAppRole ──► Role (scoped to Application)

Roles

FieldTypeDescription
idUUIDPrimary key
applicationIdUUIDApplication this role belongs to
namestringRole name (unique per application, max 64 chars)
descriptionstringOptional description (max 250 chars)
isDefaultbooleanAssigned to a user who is granted access (or auto-provisioned on a public / open-registration application) without an explicit role

Only one role per application can have isDefault = true. Creating a new default role resets the existing default.

Permissions

FieldTypeDescription
idUUIDPrimary key
applicationIdUUIDApplication this permission belongs to
resourcestringResource name, e.g. documents, reports (max 128 chars, must match /^[a-zA-Z][a-zA-Z0-9._-]*$/)
action"read" | "write"Action on the resource (default read)

Permission strings in the JWT follow this convention:

  • read action → resource (e.g. documents)
  • write action → resource.write (e.g. documents.write)

When a user holds both the read and the write permission on the same resource, only resource.write is emitted.

Admin API endpoints

All role and permission endpoints require an admin or superadmin session.

List roles with permissions

GET /api/admin/applications/:appId/roles

Returns all roles for the application, each with a permissionIds array.

Create a role

POST /api/admin/applications/:appId/roles
{
  "name": "editor",
  "description": "Can read and write documents",
  "isDefault": false
}

A duplicate name is refused with PERM_004.

Update a role

PATCH /api/admin/applications/:appId/roles/:roleId
{ "description": "Can edit content", "isDefault": false }

Only description and isDefault can be changed; the name is immutable (a payload containing name is rejected with 400 APP_001).

Delete a role

DELETE /api/admin/applications/:appId/roles/:roleId

Blocked (PERM_006) while the role is assigned to any user of the application.

Assign a permission to a role

POST /api/admin/applications/:appId/roles/:roleId/permissions
{
  "permissionId": "uuid-1"
}

One permission per call; assigning it again is a no-op. Both the role and the permission must belong to the application (PERM_002 / PERM_003 otherwise).

List permissions

GET /api/admin/applications/:appId/permissions

Create a permission

POST /api/admin/applications/:appId/permissions
{
  "resource": "documents",
  "action": "write"
}

Permission names must match /^[a-zA-Z][a-zA-Z0-9._-]*$/ (PERM_001 otherwise); a duplicate resource + action pair is refused with PERM_005.

Delete a permission

DELETE /api/admin/applications/:appId/permissions/:permissionId

JWT claims

When a client requests the roles and/or permissions scopes, the ID token, the userinfo response and a JWT access token include:

{
  "roles": ["editor"],
  "permissions": ["documents.write", "reports"]
}

Claims are populated by getUserClaims() in src/services/claims.ts. If the user has no role in the application, both arrays are empty.

Global user roles

In addition to per-application roles, every user has a global system role managed by BetterAuth's admin plugin. Roles are ranked user < admin < superadmin:

RoleCapabilities
userDefault. No admin API access.
adminCan manage users, sessions, and applications via the admin API, but only users of a lower rank (non-admin users). Can impersonate non-admin users.
superadminFull admin privileges including impersonating admin users. Cannot be deleted if it is the last superadmin.

Global roles are set via PATCH /api/admin/users/:id (or role on POST /api/admin/users) and are stored in BetterAuth's user table. Only user and admin can be assigned through the API, and only by a superadmin for admin; the first superadmin is created at startup from ADMIN_EMAIL / ADMIN_PASSWORD when no superadmin exists yet.

Copyright © 2026