Roles & Permissions
How RBAC works in Aether Labs SSO — roles, permissions, application scoping, and the seeded roles.
Roles & Permissions
Aether Labs SSO uses role-based access control (RBAC). A user holds one or more roles; each role carries a list of permissions. Permissions are the thing your app actually checks — roles are just bundles of them.
The model
user ──(user_roles)──▶ role ──(permissions[])──▶ "resource:action"
└─ application_id ─▶ scopes the role to one OAuth client- Permissions are
resource:actionstrings, e.g.users:read,oauth_clients:write,finance:view_sensitive. - Roles have a name, a description, a permission list, and an optional
application_id. A role withapplication_id = nullis global; a scoped role only applies to that OAuth client. user_roleslinks a user to a role, withassigned_by,expires_at, andis_active.
Permissions are resolved live from the database on each request. They are
not embedded in the JWT — the token carries only roles. Read permissions
from GET /api/auth/me (or
/api/rbac/users/:userId/permissions).
Permission catalog
Permissions are grouped by resource:
| Resource | Examples |
|---|---|
users | users:read, users:write, users:delete, users:manage_roles |
oauth_clients | oauth_clients:read, oauth_clients:write, oauth_clients:rotate_secret, oauth_clients:delete |
team | read/write on team members |
projects | read/write on projects |
clients | read/write on clients |
finance | finance:view, finance:view_sensitive |
accounts | read/write on financial accounts |
blogs | read/write/delete on CMS content |
leads | read/write on leads |
system | system:manage_users, system:manage_roles |
server | server-level administration |
Fetch the authoritative list at runtime from GET /api/rbac/permissions.
Seeded roles
On every boot the API ensures a default set of roles exists, each scoped to the
admin-dashboard OAuth client:
| Role | Intended for |
|---|---|
super_admin | Everything, including creating and editing roles. |
admin | Almost everything, excluding sensitive finance data. |
manager | Projects and clients, team, finance, and leads. |
support | Read-mostly access plus lead updates. |
content_writer | Blog/CMS read, write, and delete. |
Admin-tier checks
Route guards accept admin, super_admin, and manager as admin-tier.
Managing roles themselves requires super_admin.
Assigning roles
Roles are assigned through the SSO admin console (admin-web → Users) or the
API:
POST https://sso.aetherlabs.agency/api/rbac/users/:userId/roles
Authorization: Bearer <accessToken>
Content-Type: application/json
{ "roleId": "role-uuid" }Requires the users:manage_roles permission.
Bootstrapping the first admin
New users register with no roles, so there is always a chicken-and-egg problem
for the first administrator. Register a user, then grant super_admin:
pnpm --dir api bootstrap:admin --email you@example.comThe script builds the API, ensures the default roles exist, and assigns
super_admin (scoped to the admin-dashboard client) to that existing user, with an
audit log entry. In production, run it inside the api container:
node dist/scripts/bootstrapAdmin.js --email you@example.comEnforcing permissions
Frontend checks are for UX only. Always enforce on the backend:
function requirePermission(permission: string) {
return (req, res, next) => {
if (!req.user?.permissions?.includes(permission)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}See also
- Same-domain SSO — reading roles and permissions at sign-in.
- API reference — the RBAC and users endpoints.