How Logto Implements API Access Control: Token-Based RBAC and Scope Verification

Logto uses a token-based, scope-driven RBAC system built on OpenID Connect (OIDC), where JWT access tokens carry scope claims that are validated by middleware guards before every protected endpoint.

The logto-io/logto repository is an open-source identity and access management platform that protects its APIs using a layered API access control strategy. This implementation combines OIDC-compliant JWT tokens, scope-based middleware enforcement, and flexible role-based access control to secure both user-facing applications and administrative Management API endpoints.

JWT Access Tokens and Scope Claims

Logto issues JWT access tokens containing standard OIDC fields plus a critical scope claim that drives authorization decisions. The token structure is strictly defined in /packages/schemas/src/types/logto-config/oidc-provider.ts through the accessTokenPayloadGuard (lines 18-30), which ensures every token contains the required payload shape.

Key token fields include:

  • aud (audience)
  • jti (token ID)
  • exp (expiration)
  • scope (space-delimited list of granted permissions)

The scope claim lists granted permissions such as openid, profile, or custom API scopes like api:read. For full Management API access, Logto reserves the All scope within the PredefinedScope enum defined in /packages/schemas/src/types/user.ts (lines 81-85).

import { accessTokenPayloadGuard } from '@logto/schemas';

// Example payload conforming to the guard
const payload = {
  jti: 'unique-token-id',
  aud: 'logto',
  clientId: 'machine-client',
  accountId: 'user-123',
  grantId: 'grant-456',
  gty: 'client_credentials',
  kind: 'AccessToken',
  scope: 'api:read api:write',  // Space-delimited scopes
};

Middleware Enforcement with Koa-Auth

Every incoming request to a protected Logto endpoint passes through the Koa-auth middleware located in /packages/core/src/middleware/koa-auth/index.ts. This middleware extracts the bearer token from the Authorization header, validates it against the accessTokenPayloadGuard, and verifies that the token's scope claim satisfies the endpoint's required permissions (lines 1-12).

If the scope check fails, the middleware returns a 403 Forbidden error immediately, preventing unauthorized access before the route handler executes. For routes requiring specific permissions, Logto uses a requireScopes helper that wraps the validation logic.

import Router from 'koa-router';
import { requireScopes } from '#src/middleware/koa-auth';

const router = new Router();

router.get(
  '/api/protected-resource',
  requireScopes(['api:read']),  // Middleware validates token.scope
  async (ctx) => {
    ctx.body = { data: 'Protected content' };
  }
);

RBAC: Roles and Predefined Scopes

Logto implements Role-Based Access Control (RBAC) by storing roles and permissions in the database and mapping them to the PredefinedScope values. When issuing tokens for machine-to-machine clients, Logto derives the token's scopes from the client's assigned roles.

The seeding script in /packages/schemas/src/seeds/management-api.ts (lines 47-54) demonstrates this by creating a default "Management API access" role linked to the All scope. This role grants comprehensive access to administrative functions and is automatically attached to the built-in admin tenant during initialization.

The PredefinedScope enum in /packages/schemas/src/types/user.ts (lines 81-84) acts as the source of truth for system-level permissions, ensuring consistent scope naming across the platform.

Application-Level Access Control

Beyond RBAC, Logto supports fine-grained application-level access control through the ApplicationAccessControl object. This system allows administrators to define explicit allow-lists containing specific user IDs, user role IDs, organization IDs, and organization role rules.

The utilities in /packages/core/src/routes/applications/application-access-control/utils.ts manage these rules:

  • ApplicationAccessControl interface (lines 6-15) defines the rule structure
  • assertApplicationAccessControlHasRules (lines 17-24) validates that at least one rule exists before the feature can be enabled

When application-level ACL is active, the accessControlGuard reads these rule tables to verify if the caller's token (and its associated user/role information) matches any allowed entry.

import {
  ApplicationAccessControl,
  assertApplicationAccessControlHasRules,
} from '#src/routes/applications/application-access-control/utils';

const accessControl: ApplicationAccessControl = {
  userIds: ['user-1', 'user-2'],
  userRoleIds: ['role-admin'],
  organizationIds: ['org-123'],
  organizationRoleRules: [],
};

// Throws 422 if no rules are defined
assertApplicationAccessControlHasRules(accessControl);

Securing the Management API

The Management API represents Logto's most privileged endpoint set, requiring tokens that carry the PredefinedScope.All scope. As implemented in /packages/core/src/middleware/koa-auth/index.ts (lines 110-112), endpoints within the Management API namespace explicitly check for this "all-access" scope.

During system initialization, the seed script in /packages/schemas/src/seeds/management-api.ts (lines 47-55) creates a dedicated Management API role pre-configured with the All scope. This role is automatically provisioned for the admin tenant, ensuring administrative functions remain accessible only to properly authorized automation scripts and console users.

Summary

Frequently Asked Questions

How does Logto validate API access token scopes?

Logto validates scopes through the Koa-auth middleware located in /packages/core/src/middleware/koa-auth/index.ts. The middleware extracts the JWT from the Authorization header, verifies its signature and structure against the accessTokenPayloadGuard, and checks that the token's scope claim includes the permissions required by the endpoint. If validation fails, the middleware returns a 403 Forbidden response before the route handler executes.

What is the difference between RBAC and application-level access control in Logto?

RBAC (Role-Based Access Control) operates at the system level, mapping machine-to-machine clients and users to predefined scopes like All or api:read through database roles. Application-level access control provides finer granularity within specific applications, allowing administrators to create explicit allow-lists of user IDs, role IDs, and organization IDs that can access a particular application, as defined in /packages/core/src/routes/applications/application-access-control/utils.ts.

How is the Logto Management API protected?

The Management API requires access tokens carrying the PredefinedScope.All scope, which grants comprehensive administrative privileges. This scope is defined in /packages/schemas/src/types/user.ts and assigned through a dedicated Management API role created during system initialization in /packages/schemas/src/seeds/management-api.ts. The middleware explicitly checks for this scope (lines 110-112 in /packages/core/src/middleware/koa-auth/index.ts) to protect administrative endpoints.

What fields are included in a Logto access token?

According to the accessTokenPayloadGuard in /packages/schemas/src/types/logto-config/oidc-provider.ts, Logto access tokens include standard OIDC fields such as jti (token ID), aud (audience), exp (expiration), clientId, accountId, and grantId, plus a critical scope field containing space-delimited permissions. The kind field is set to AccessToken to distinguish these from other JWT types in the system.

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 →