C3 AI Documentation Home

Restrict an identity to a specific Role

A restricted identity is a mechanism that lets a User issue a SessionToken which acts on their behalf but cannot exceed a bounded set of privileges. It is the platform-native way to give an agent, an automation, or any delegated caller an identity that is:

  • Bound to a user — the token still authenticates as the user, so the audit trail attributes actions to that user.
  • Capped by a ceiling role — even if the user has broad privileges, the restricted session is limited to the intersection of the user's permissions and the ceiling role's permissions.
  • Tied to a restricted identity id — a stable string (for example the agent or session id) that is carried on every action dispatched under the token and is preserved across nested dispatches.

This implements the principle of least privilege for delegated callers: a compromised agent or narrow delegation cannot call actions the ceiling role does not grant, even if the underlying user is a cluster administrator.

When to use restricted identities

Use a restricted identity whenever code is going to run as a user, but should not inherit the user's full privileges. Common scenarios include:

  • Agent and copilot flows. An agent process generates a token to call back into the C3 server on a user's behalf. You scope the token with a ceiling role (for example one that only allows the agent's own service and session methods) so an agent pod cannot invoke administrative actions under the user.
  • External callbacks and webhooks. An external system calls back into the platform to complete a workflow started by a user. The callback is given a token tied to the user but capped to a role that only allows the callback-relevant methods.
  • Scoped automation. A scheduled job runs under a user's identity but must only be allowed to touch a narrow slice of the data model.

How restriction is enforced

Restriction is carried on the session token and enforced by the Authorizer on every action dispatch. A token without a restricted identity behaves exactly like a normal user session token.

Action#isRestricted() returns true whenever an action is dispatched under a restricted token. Use it in diagnostics, logging, or custom authorization code that needs to know a call is running under a ceiling. The restriction is inherited by every child action spawned under the same dispatch — you cannot escape it by nesting calls or spawning async jobs.

Authorization

The Authorizer#authorize method applies the following rules to a restricted action:

  1. Cluster admin no longer bypasses. If Ctx.isClusterAdmin() or action.isClusterAdmin() is true but the action is restricted, authorization is still evaluated against permissions. Cluster administrators calling through a restricted token are subject to the same ceiling as any other user.
  2. Security-leveled actions are denied. A restricted context cannot invoke any action whose permission carries a security level, regardless of who the user is. This guards the most sensitive platform operations from ever being dispatched through a restricted token.
  3. User permissions AND ceiling role permissions. The user's permission set and the ceiling role's permission set are computed independently and AND'ed: the action is authorized only if both would authorize it. If the restrictedRoleId does not resolve to a Role, the action is denied with a warning in the server log.

Cross-app safety

A session token carrying a restricted identity is only valid in the App that issued it. If such a token is presented to a different app, validation fails with Err.Authn.ServerError#invalidRestrictedClaimCrossApp (HTTP 401). Session refresh preserves the restriction, so a restricted caller cannot shed the ceiling by regenerating its token.

Issuing a restricted session token

Any type that mixes RestrictedIdentity exposes issueSessionToken(restrictedId, roleId) which signs a JWT for the current user carrying the restricted claims.

JavaScript
RestrictedIdentity.issueSessionToken(restrictedId, 'My.Pkg.CustomRole');
// pass signedToken to the process; it will authenticate as the
// current user but bounded by the My.Pkg.CustomRole ceiling role.

The restrictedId is required; it is typically a stable identifier for the delegated caller (agent pod id, automation id, session id). The roleId is optional; when omitted, only the restricted identity claim is set and authorization still evaluates user permissions normally — the token is marked restricted (so cluster-admin bypass and security-leveled actions are still blocked) but not capped to a specific role.

The returned string is the signed, serialized JWT. Hand it to the delegated caller exactly as you would a normal session token. Callers authenticate by presenting it in the Authorization header.

Modeling a ceiling role

A ceiling role is an ordinary Role. Design it the way you would any least-privilege role, keeping these points in mind:

  • Grant only the actions the delegated caller needs to perform. Remember that both the user's role and the ceiling role must allow an action; you cannot use the ceiling role to grant access the user does not already have.
  • Do not add security-leveled permissions; they will be denied anyway when the action is invoked through a restricted context.
  • Use the ceiling role exclusively for restricted-identity flows. Assigning it to users directly works but defeats the purpose, since those users will also have the role outside of restricted tokens.

For general role design guidance see Roles in the C3 Agentic AI Platform and Define Permissions.

Was this page helpful?