Roles & permissions
- Written for
- + Written for
- Deprecated
- + Deprecated
- Applies to
- + Applies to
Roles & permissions
Atlas gives you two authorization tools. Roles & permissions (RBAC) is the coarse, per-organization one — "admins can manage billing; members can't." For per-object relationship checks, see Fine-grained authorization.
The server is the boundary. Client helpers decide what a user sees; only a check on the request decides what they can do.
Keys are the contract
Keys travel in the session token and get hardcoded into your checks, so their shape is fixed and they are immutable once created:
A role key is
org:<name>— e.g.org:admin,org:member. It names who someone is.A permission key is
org:<resource>:<action>— e.g.org:billing:manage. It names what they may do.
The sys_ prefix is reserved for Atlas's built-ins (e.g. org:sys_memberships:manage). Every instance is seeded with two system roles: org:admin (holds every permission) and org:member (read-only). You can relabel but not delete them.
What reaches the session
When a user has an active organization, their role and the union of its permissions (direct and group-granted) are minted into the session JWT:
{
"sub": "user_2a…",
"org_id": "org_9f…",
"org_role": "org:admin",
"org_permissions": ["org:sys_memberships:manage", "org:billing:manage"]
}Your code reads authorization straight off the token — no call back to Atlas on the hot path. A change takes effect within one token lifetime.
Endpoints
Method & path | Scope | Notes |
|---|---|---|
|
| Roles + permissions + member counts |
|
| Create a custom role (keys namespaced; |
|
| Relabel (the key is immutable) |
|
| Replace a role's permission set (system roles are fixed) |
|
| Delete an unused role; one members hold → |
|
| Permissions available on this instance |
|
| Define a custom permission ( |
|
| Update description/metadata |
|
| Delete; removed from every role that held it |
Manage from your backend
await atlas.roles.create({
key: 'org:auditor',
name: 'Auditor',
permissions: ['org:logs:read', 'org:billing:read'],
});
// Retire a role people still hold — move its members first, atomically
await atlas.roles.delete('role_123', { reassignTo: 'role_member' });
await atlas.permissions.create({ key: 'org:reports:export', name: 'Export reports' });
await atlas.permissions.delete('perm_456'); // 409 ROLE_IN_USE if a role still grants itCheck on the server
The verified request carries has() (boolean) and protect() (throws ForbiddenError):
const auth = await atlas.authenticateRequest(req);
if (!auth.ok) return res.status(401).end();
if (auth.has({ permission: 'org:billing:manage' })) { /* … */ }
if (auth.has({ anyPermission: ['org:billing:manage', 'org:billing:read'] })) { /* … */ }
auth.protect({ role: 'org:admin' }); // 403 if unmetIn Next.js, auth() exposes the same has() / protect() in server components and route handlers.
Gate UI on the client
A rendering aid, never a security boundary — always keep the matching server check.
import { Protect, useAuth } from '@atlas/react';
<Protect permission="org:billing:manage" fallback={<Locked />}>
<BillingSettings />
</Protect>;
const { has } = useAuth();
if (has({ anyPermission: ['org:billing:manage', 'org:billing:read'] })) { /* … */ }@atlas/react-native exposes the same useAuth().has(condition), and @atlas/js ships a framework-agnostic has(claims, condition). All of them evaluate the identical condition.
IdP-driven roles
Grant a directory group an org role and its members inherit the permissions — so your IdP drives role assignment through SCIM. See Organizations → group role grants.