Setting Up RBAC with Logto Organization Roles and Scopes: Complete Implementation Guide

Logto implements organization-level RBAC through four relational tables that define organizations, roles, scopes, and user assignments, exposing this data via special OIDC scopes (urn:logto:scope:organizations and urn:logto:scope:organization_roles) that inject claims into ID tokens for fine-grained access control.

Setting up RBAC with Logto organization roles and scopes enables multi-tenant applications to enforce fine-grained permissions at the organization level. In the logto-io/logto repository, this architecture separates user memberships from role definitions and scope assignments using a relational schema defined in SQL migration files. This guide explains the database schema, OIDC claim mapping, and Management API endpoints required to implement organization-level RBAC.

Understanding the Organization RBAC Data Model

Logto persists organization-level RBAC metadata across four main tables in packages/schemas/tables/. This schema ensures referential integrity between organizations, roles, scopes, and users.

The Four Core Tables

Organization Scopes and URN Handling

Logto defines special OIDC scopes and URN formats to standardize organization identification across tokens.

Special OIDC Scopes

In packages/toolkit/core-kit/src/openid.ts, Logto enumerates two organization-related scopes in the UserScope enum:

  • urn:logto:scope:organizations – Returns the list of organization IDs a user belongs to
  • urn:logto:scope:organization_roles – Returns the roles the user has inside each organization

URN Format and Helpers

Organizations use a deterministic Uniform Resource Name (URN) format: urn:logto:organization:<orgId>. The openid.ts file exports helper functions buildOrganizationUrn and getOrganizationIdFromUrn for consistent conversion between organization IDs and URN strings.

API Surface and Token Claims

The RBAC system exposes organization data through REST endpoints and injects authorization claims into OIDC tokens.

Management API Endpoints

The endpoint defined in packages/core/src/routes/admin-user/organization.ts provides:

  • GET /users/:userId/organizations – Returns an array of organizations with embedded role data for the specified user

Additional Management API endpoints (referenced in packages/core/src/routes/role.openapi.json) allow creating organizations, defining roles, attaching scopes, and assigning roles to users.

ID Token Claim Mapping

When a client requests organization scopes, Logto injects claims into the ID token or userinfo response through extendedIdTokenClaimsByScope:

  • organizations – Array of organization IDs
  • organization_data – Full organization objects
  • organization_roles – Role IDs per organization

Implementation Workflow

Setting up RBAC requires configuring the database layer through the Management API, then requesting the appropriate scopes during authentication.

Step-by-Step Setup Process

  1. Create an organization – Call POST /api/organizations to generate an organization record
  2. Define roles – Store role definitions in organization_roles with type: "User"
  3. Attach scopes – Link scopes to roles via organization_role_scope_relations
  4. Assign users – Ensure user membership in organization_user_relations, then link to roles in organization_role_user_relations
  5. Request tokens – Include urn:logto:scope:organizations or urn:logto:scope:organization_roles in authorization requests
  6. Verify access – Resource servers validate the access token and check role-scope relations in the database

Practical Code Examples

The following TypeScript examples demonstrate interacting with the Management API to configure organization RBAC.

Creating Organizations and Roles

import fetch from 'node-fetch';

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());

// Create an organization
const org = await api('api/organizations', {
  method: 'POST',
  body: JSON.stringify({
    name: 'Acme Corp',
    description: 'Acme Corp organization',
  }),
});

// 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', // Required by check_organization_role_type
  }),
});

// 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'],
    }),
  }
);

// Add user to organization and assign role
await api(`api/organizations/${org.id}/users`, {
  method: 'POST',
  body: JSON.stringify({ userId: 'user-123' }),
});

await api(
  `api/organizations/${org.id}/users/user-123/roles`,
  {
    method: 'POST',
    body: JSON.stringify({ roleId: role.id }),
  }
);

Requesting Organization Tokens

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 user to authUrl, exchange code for tokens
// ID token will contain organizations and organization_roles claims

Summary

  • Logto implements organization-level RBAC through four tables: organizations, organization_roles, organization_role_scope_relations, and organization_role_user_relations
  • Special OIDC scopes urn:logto:scope:organizations and urn:logto:scope:organization_roles enable claim injection into ID tokens
  • The URN format urn:logto:organization:<orgId> standardizes organization identification via helpers in openid.ts
  • Role types must be set to "User" as enforced by check_organization_role_type
  • Management API endpoints in packages/core/src/routes/admin-user/organization.ts provide programmatic access to organization membership data

Frequently Asked Questions

What is the difference between organization roles and user roles in Logto?

Organization roles are scoped to specific organizations and stored in organization_roles, while user roles are global. Organization roles use the organization_role_user_relations table to link users within specific organizational contexts, enabling multi-tenant permission isolation.

How does Logto validate that a user can be assigned a role in an organization?

The database schema enforces referential integrity through foreign-key constraints in organization_role_user_relations. A user must already exist in organization_user_relations (the membership table) before they can be assigned a role, preventing orphaned role assignments.

What claims appear in the ID token when requesting organization scopes?

When requesting urn:logto:scope:organizations and urn:logto:scope:organization_roles, Logto injects three claims: organizations (array of IDs), organization_data (full objects), and organization_roles (role mappings). These are mapped via extendedIdTokenClaimsByScope in packages/toolkit/core-kit/src/openid.ts.

Can I use custom scopes with organization roles?

Yes. Custom scopes defined in your API resource can be attached to organization roles through the organization_role_scope_relations table. When assigning scopes via the Management API (POST /api/organizations/{orgId}/roles/{roleId}/scopes), include your custom scope identifiers in the request body.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →