# How to Implement RBAC with Roles and Scopes in Logto

> Easily implement RBAC with roles and scopes in Logto. Discover how Logto's relational tables and OAuth scopes grant granular user permissions for enhanced security.

- Repository: [Logto/logto](https://github.com/logto-io/logto)
- Tags: how-to-guide
- Published: 2026-07-05

---

**Logto implements organization-level RBAC through four relational tables that map users to roles and scopes, exposing permissions via special OAuth scopes and ID token claims.**

Logto provides a comprehensive role-based access control (RBAC) system that operates at the organization level, allowing multi-tenant applications to define granular permissions within isolated organizational contexts. The `logto-io/logto` repository stores RBAC metadata across four core database tables and exposes management APIs to configure roles, scopes, and user assignments programmatically.

## Understanding the RBAC Data Architecture

Logto's RBAC implementation relies on a relational schema that connects organizations, roles, scopes, and users. According to the source code in `packages/schemas/tables/`, the architecture consists of four primary tables:

- **`organizations`** – Stores organization metadata and defines the organizational boundary
- **`organization_roles`** – Contains role definitions scoped to specific organizations (e.g., `admin`, `member`)
- **`organization_role_scope_relations`** – Maps which permissions (scopes) belong to each role
- **`organization_role_user_relations`** – Links users to their assigned roles within an organization

This design enforces that roles and permissions are strictly isolated per organization, with foreign-key constraints ensuring users must be organization members before receiving role assignments.

## Organization Scopes and URN Format

Logto defines two special OAuth scopes for accessing organization data, enumerated in [`packages/toolkit/core-kit/src/openid.ts`](https://github.com/logto-io/logto/blob/main/packages/toolkit/core-kit/src/openid.ts):

- **`urn:logto:scope:organizations`** – Returns the list of organization IDs a user belongs to
- **`urn:logto:scope:organization_roles`** – Returns the roles the user holds inside each organization

Organizations utilize a Uniform Resource Name (URN) format of `urn:logto:organization:<orgId>`. The source code exports helper functions `buildOrganizationUrn` and `getOrganizationIdFromUrn` in [`openid.ts`](https://github.com/logto-io/logto/blob/main/openid.ts) to handle deterministic conversion between organization IDs and URN strings.

When a client requests these organization scopes, Logto injects three key claims into the ID token (or userinfo endpoint):

1. **`organizations`** – Array of organization IDs
2. **`organization_data`** – Full organization objects
3. **`organization_roles`** – Role IDs mapped per organization

The claim list is built from `extendedIdTokenClaimsByScope` in the same file.

## Step-by-Step RBAC Implementation Workflow

Implementing RBAC in Logto requires six sequential steps involving the Management API and client-side token requests.

### 1. Create an Organization

An admin calls the Management API to create an organizational container. This inserts a record into the `organizations` table.

### 2. Define Organization Roles

Create roles within the organization scope. Each role is stored in `organization_roles` with a type constraint of `"User"` (validated by `check_organization_role_type`). Unlike global application roles, these are isolated to the specific organization.

### 3. Attach Scopes to Roles

Define granular permissions (such as `read:projects` or `write:resources`) and link them to roles through the `organization_role_scope_relations` table. This creates the permission matrix that determines what each role can access.

### 4. Assign Users to Organizations

Before assigning roles, users must be added to the organization via the `organization_user_relations` table. The foreign-key constraint on `organization_role_user_relations` enforces this membership requirement.

### 5. Assign Roles to Users

Link users to their specific roles using the `organization_role_user_relations` table. A single user can hold multiple roles within the same organization.

### 6. Request Tokens with Organization Scopes

Clients include `urn:logto:scope:organizations` and `urn:logto:scope:organization_roles` in the authorization request. Logto validates the request and embeds the corresponding claims in the ID token.

### 7. Verify on Resource Server

The resource server extracts the access token, validates the granted scopes, and checks the user's role-scope relations in the database to enforce authorization decisions.

## API Endpoints and User Organization Data

The Logto core exposes endpoints for managing RBAC relationships. In [`packages/core/src/routes/admin-user/organization.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/admin-user/organization.ts), the **GET** `/users/:userId/organizations` endpoint returns an array of organizations with embedded role data for a specific user.

Management API endpoints (documented in [`packages/core/src/routes/role.openapi.json`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/role.openapi.json)) allow administrators to:

- Create organizations (`POST /api/organizations`)
- Define roles (`POST /api/organizations/{id}/roles`)
- Attach scopes (`POST /api/organizations/{id}/roles/{roleId}/scopes`)
- Assign users to roles (`POST /api/organizations/{id}/users/{userId}/roles`)

## Complete Implementation Example

Below is a TypeScript implementation using the Logto Management API. Replace `YOUR_ACCESS_TOKEN` with an admin token and configure your tenant endpoint accordingly.

```typescript
import fetch from 'node-fetch';

// Helper to call Logto Management API
const api = (path: string, init: RequestInit = {}) =>
  fetch(`https://api.logto.io/${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer YOUR_ACCESS_TOKEN`,
      'Content-Type': 'application/json',
    },
  }).then((res) => res.json());

// 1️⃣ Create an organization
const org = await api('api/organizations', {
  method: 'POST',
  body: JSON.stringify({
    name: 'Acme Corp',
    description: 'Acme Corp organization',
  }),
});
console.log('Organization created:', org.id);

// 2️⃣ Define a role inside the organization
const role = await api(`api/organizations/${org.id}/roles`, {
  method: 'POST',
  body: JSON.stringify({
    name: 'admin',
    description: 'Full access inside Acme',
    type: 'User', // Must be "User" for organization roles
  }),
});
console.log('Role created:', role.id);

// 3️⃣ Attach scopes to the role
await api(
  `api/organizations/${org.id}/roles/${role.id}/scopes`,
  {
    method: 'POST',
    body: JSON.stringify({
      scopes: [
        'read:projects',
        'write:projects',
      ],
    }),
  }
);
console.log('Scopes attached to role');

// 4️⃣ Add a user to the organization (if not already a member)
await api(`api/organizations/${org.id}/users`, {
  method: 'POST',
  body: JSON.stringify({ userId: 'user-123' }),
});
console.log('User added to organization');

// 5️⃣ Assign the role to the user
await api(
  `api/organizations/${org.id}/users/user-123/roles`,
  {
    method: 'POST',
    body: JSON.stringify({ roleId: role.id }),
  }
);
console.log('Role assigned to user');

// 6️⃣ Request an access token with organization scopes (client side)
import { AuthorizationCode } from '@logto/client';
const client = new AuthorizationCode({
  clientId: 'YOUR_CLIENT_ID',
  redirectUri: 'https://your.app/callback',
  issuer: 'https://logto.io',
});
const authUrl = client.getAuthorizationUri({
  scope: 'openid profile urn:logto:scope:organizations urn:logto:scope:organization_roles',
});
// Redirect the user to `authUrl`, then exchange the code for tokens.
// The resulting ID token will contain `organizations` and `organization_roles` claims.

```

## Summary

- **Four-table architecture** – Logto stores RBAC data in `organizations`, `organization_roles`, `organization_role_scope_relations`, and `organization_role_user_relations` tables.
- **Organization isolation** – Roles and scopes are scoped to specific organizations, not global to the application.
- **Special OAuth scopes** – `urn:logto:scope:organizations` and `urn:logto:scope:organization_roles` trigger the inclusion of organization claims in ID tokens.
- **URN format** – Organizations use `urn:logto:organization:<orgId>` format with helper functions available in [`openid.ts`](https://github.com/logto-io/logto/blob/main/openid.ts).
- **Enforcement** – Resource servers validate access tokens against the database relations to enforce permissions.

## Frequently Asked Questions

### What database tables store RBAC data in Logto?

Logto stores RBAC metadata across four tables defined in `packages/schemas/tables/`: `organizations` for organization data, `organization_roles` for role definitions, `organization_role_scope_relations` for the permission matrix, and `organization_role_user_relations` for user-role assignments. These tables enforce referential integrity through foreign-key constraints that ensure users are organization members before receiving role assignments.

### How do organization-scoped roles differ from global roles in Logto?

Organization roles are isolated to specific organizations and stored in the `organization_roles` table with a required type of `"User"`. They are managed through organization-specific Management API endpoints (e.g., `/api/organizations/{id}/roles`). Global roles, by contrast, apply application-wide and are not constrained to organizational boundaries. The function `check_organization_role_type` validates that organization roles use the correct type enum.

### What claims does Logto add to the ID token for organization access?

When requesting the `urn:logto:scope:organizations` and `urn:logto:scope:organization_roles` scopes, Logto injects three claims into the ID token: `organizations` (array of organization IDs), `organization_data` (full organization objects), and `organization_roles` (role IDs per organization). These claims are mapped through the `extendedIdTokenClaimsByScope` configuration in [`packages/toolkit/core-kit/src/openid.ts`](https://github.com/logto-io/logto/blob/main/packages/toolkit/core-kit/src/openid.ts).

### How do I validate organization permissions in my resource server?

The resource server extracts the access token and verifies the presence of required scopes (such as `urn:logto:scope:organization_roles`). For fine-grained permission checks, the server queries the `organization_role_scope_relations` table to verify that the user's assigned roles include the necessary scopes for the requested resource. The scope validation logic references the `protectedAppAdditionalScopes` constant defined in the core toolkit.