UIAM · Agents

Agents should not work with a human’s token

Handing an agent a human user’s token is handing a master key to a behaviour you do not fully understand. We use RFC 8693 to write “who acts for whom” into the token itself.

The first plan for hooking an AI agent into an enterprise system is usually this: let the user log in once and hand the resulting token to the agent to use. It runs, but it leaves three questions you cannot answer — whose permissions did the agent exercise? Can the agent keep working after the user revokes its authorization? And when something goes wrong, how do you tell a human action from an agent action?

Agents are principals, not tools

The first step is to model the agent as its own principal. Its tokens carry principal_type=machine — the same kernel as human users, a different principal type. An agent has its own credentials, its own roles, its own call quota.

uiam-agent — the MACHINE principal
01// Agent 不再伪装成 USER,它有独立的 subject_type
02{
03 "sub": "agent:sales-copilot",
04 "principal_type": "machine",
05 "tenant_id": "acme",
06 "roles": ["order:read", "customer:read"],
07 "quota": { "tool_calls_per_min": 120 }
08}

The delegation chain: intersect permissions, never union them

When an agent acts on behalf of a user, it goes through an RFC 8693 Token Exchange to obtain a fresh token. The crux is how permissions are computed: the new token’s permissions are the intersection of “the app permissions granted to the agent” and “the permissions that user actually holds”, then trimmed to a ceiling by maxPermissionLevel.

This point matters enormously. Take the union and the agent becomes a permission amplifier — it ends up holding permissions the user never had. Take the intersection and the agent can never do more than the user who delegated to it.

TokenExchangeServiceImpl — the permission intersection
01val appPerms = appPermissionResolver.resolve(clientId)
02val userPerms = userPermissionResolver.resolve(subjectToken.sub)
03
04// 交集:Agent 不能比被委托的用户做得更多
05val granted = appPerms.intersect(userPerms)
06 .filter { it.level <= maxPermissionLevel }
07
08// act claim 记录委托关系,多跳场景可嵌套
09val act = ActClaim(sub = subjectToken.sub, nested = subjectToken.act)

Tool-level authorization lives in the code

Which tools an agent may call is not written in a document for people to obey — it is written on annotations and enforced by the framework. The agent_tools registry records each tool’s ownership and permission requirements, and every call is verified against them before it runs.

uiam-agent — tool-level authorization
01@RequiresToolPermission("order:refund")
02@PostMapping("/tools/order-refund/invoke")
03fun invokeRefund(@RequestBody req: ToolRequest): ToolResult {
04 // 到这里说明调用方持有该工具权限,且未超配额
05 return toolExecutionService.execute(req)
06}

What it costs

  • Every agent must be registered and assigned roles up front — no “get it running first, sort it out later”
  • The delegation chain adds one more token exchange: a longer path and more complex troubleshooting
  • Nested act claims in multi-hop delegation need careful handling, or the intermediate delegation relationships get lost

What these costs buy is this: for every data access an agent makes, you can answer “who authorized it, which tool was used, which permissions were exercised”. In compliance-driven settings, that ability to answer is the price of admission.

More Notes
Get in touch

Hand the complexity of identity, agents and private domain to one governable kernel

Whether you are replacing an existing IAM, building an agent platform, or trying to make private-domain operations actually work — start with a 30-minute architecture call. We will first judge whether this is the kind of problem we are good at, and say so plainly if it is not.