API Reference
OIDC
Platform

Roles & permissions

AdminUpdated Sep 11, 2026

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

GET /v1/roles

organizations:read

Roles + permissions + member counts

POST /v1/roles

organizations:write

Create a custom role (keys namespaced; sys_ reserved)

PATCH /v1/roles/:id

organizations:write

Relabel (the key is immutable)

PUT /v1/roles/:id/permissions

organizations:write

Replace a role's permission set (system roles are fixed)

DELETE /v1/roles/:id

organizations:write

Delete an unused role; one members hold → ROLE_IN_USE (pass reassign_to to move members first)

GET /v1/permissions

organizations:read

Permissions available on this instance

POST /v1/permissions

organizations:write

Define a custom permission (org:invoices:manage)

PATCH /v1/permissions/:id

organizations:write

Update description/metadata

DELETE /v1/permissions/:id

organizations:write

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 it

Check 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 unmet

In 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.

Was this page helpful?