AI Agents Shouldn't Inherit User Privileges: Designing Secure Delegation with OAuth 2.0

An implementation of user-to-agent delegation, A2A authentication, and least-privilege authorization using Keycloak and RFC 8693.

· 2268 words · 11 minute read

1. The Security Problem: When Agents Act on Behalf of Users 🔗

Organizations are increasingly introducing AI agents into their existing business processes. For larger enterprises, especially those operating in regulated industries, this introduces a new challenge: how do we allow AI agents to interact with existing systems without undermining established identity and access management controls?

The fundamental security principles haven’t changed. We still need to enforce least privilege, maintain accountability, and ensure that access to sensitive information and operations is properly controlled.

What changes is who—or what—is performing the action.

Alice works in the finance department. She has access to the company’s financial system, where she can read and modify invoices, view payments, and approve them.

To improve productivity, the company introduces an AI agent that can interact with the financial system on her behalf. Alice can now ask the agent to retrieve an invoice, summarize its contents, or update certain information without navigating the application herself.

But there’s an important distinction: Alice is no longer interacting directly with the financial system. The agent is acting on her behalf.

For example, Alice may be authorized to approve payments, but the company wants payment approval to remain a human responsibility. The agent should therefore be able to read payment information without being allowed to approve payments—even when acting on behalf of an authorized user.

The opposite must also hold true: if a user doesn’t have permission to access a particular resource, an AI agent shouldn’t provide an alternative path to it.

This leads to a fundamental question:

How can we allow an AI agent to act on behalf of a user while ensuring that neither the user’s nor the agent’s authorization boundaries are exceeded?

In this article, we’ll explore an architecture that addresses this problem using OAuth 2.0 delegation, agent-to-agent communication, and authorization enforcement at the resource server. We’ll also examine what these mechanisms protect against—and where their limitations lie.

2. A Delegated Authorization Model 🔗

flowchart LR
    U(["Alice (user)"])
    UI["UI Client"]
    P["Primary Agent"]
    F["Finance Agent"]
    API["Finance API"]
    K["Keycloak"]

    U --> UI
    UI --> P
    P -->|A2A| F
    F -->|Delegated access| API

    K -.->|OIDC Authentication| UI
    K -.->|Delegation & consent| P
    K -.->|Token exchange| F

    classDef agent fill:#D9E7FC,stroke:#7099DD,color:#111111
    classDef identity fill:#DDF0D8,stroke:#83B978,color:#111111
    classDef default fill:#FFFFFF,stroke:#333333,color:#111111

    class P,F agent
    class K identity

This is the architecture implemented in our agent-identity-lab. Alice sends requests through a UI client to the Primary Agent, which delegates finance tasks over A2A. Keycloak manages identities and delegation; the Finance API enforces business authorization.

Neither the user’s privileges nor the agent’s capabilities are sufficient on their own. Effective access is constrained by the user’s permissions, the agent’s permissions, the delegated token scopes, and resource-specific policies.

This means an agent cannot use a user’s broader privileges to approve a payment it is not allowed to approve. Nor can the agent give a user access to invoices the user is forbidden to read.

The next sections explain the trust boundaries and the OAuth mechanisms behind this model.

3. Reference Architecture: From User to Resource Server 🔗

The separation between agents is a security decision, not just a way to organize code.

The Primary Agent handles conversations, manages destination-specific consent, and routes tasks. It has no direct access to the financial database or the Finance API. The Finance Agent owns the finance tools and has its own OAuth identity, allowing a separate team to develop and operate it without giving the Primary Agent finance privileges.

Tokens also respect service boundaries. A token intended for the Primary Agent cannot be reused downstream. The Finance Agent accepts a Finance-specific user token together with a separate token identifying the calling Primary Agent. The Finance API, in turn, requires a token representing the delegation to the Finance Agent and intended for the API itself.

Finally, Keycloak and the Finance API make different decisions. Keycloak authenticates the parties and controls token issuance and delegation. The Finance API knows which invoices Alice is entitled to access and what the Finance Agent may do with them. A valid delegated token establishes an identity context, not unrestricted access to business resources.

This division lets IAM teams provide shared identity infrastructure while application owners retain control of their business authorization policies.

4. Implementing Delegation with OAuth 2.0 and RFC 8693 🔗

The implementation uses OpenID Connect, OAuth 2.0 and RFC 8693 — OAuth 2.0 Token Exchange. The sequence below shows how the two agent identities and the user’s delegated authority are carried across services.

sequenceDiagram
    actor Alice
    participant UI as UI Client
    participant Primary as Primary Agent
    participant KC as Keycloak
    participant Finance as Finance Agent
    participant API as Finance API

    Note over Alice,KC: 1. Authentication
    Alice->>UI: Sign in
    UI->>KC: OIDC authentication
    KC-->>UI: User access token (aud=primary-agent)

    Alice->>UI: Request finance task
    UI->>Primary: Request + user token
    Note right of Primary: Validate JWT (aud=primary-agent)

    opt No valid delegation available
        Note over Alice,KC: 2. Delegation consent
        Primary->>KC: Device Authorization Grant (finance-agent)
        KC-->>Primary: device_code + user_code
        Primary-->>UI: Consent link
        UI-->>Alice: Display consent link
        Alice->>KC: Approve delegation with same account
        Alice->>UI: Retry request
        UI->>Primary: Request + user token
        Primary->>KC: Poll token endpoint (device_code)
        KC-->>Primary: User token (aud=finance-agent, may_act)
        Note right of Primary: Validate token and matching user sub
    end

    Note over Primary,Finance: 3. Authenticated A2A communication
    Primary->>KC: Client credentials (primary-agent)
    KC-->>Primary: Primary service token (aud=finance-agent)
    Primary->>Finance: A2A SendMessage + user token + service token
    Note right of Finance: Validate both tokens and Primary azp

    Note over KC,Finance: 4. OAuth 2.0 Token Exchange (RFC 8693)
    Finance->>KC: Client credentials (finance-agent)
    KC-->>Finance: Finance actor token
    Finance->>KC: Token exchange (subject_token + actor_token)
    KC-->>Finance: Delegated token (sub=Alice, act=finance-agent, aud=finance-api)
    Note right of Finance: Validate delegated subject and actor

    Note over Finance,API: 5. Resource authorization
    Finance->>API: API request + delegated token
    Note right of API: Validate JWT, scopes and business permissions
    API-->>Finance: ALLOW / DENY + result
    Finance-->>Primary: A2A response
    Primary-->>UI: Assistant response
    UI-->>Alice: Result

Alice signs in through the UI client using OpenID Connect. Her initial access token is intended for the Primary Agent.

On her first finance request, the Primary Agent initiates an OAuth 2.0 Device Authorization Grant and returns a Keycloak consent link. Alice approves the delegation using the same account, then resends her request. The Primary Agent retrieves a separate user token intended exclusively for the Finance Agent.

It verifies that the token’s sub matches the authenticated chat user, its audience is finance-agent, and its may_act claim identifies the Finance Agent as an authorized actor. Here, may_act expresses permission to act; it does not mean the agent has already acted. Alice’s original Primary Agent token is never forwarded downstream.

Authenticating the A2A Request 🔗

A2A transports the task, not the authorization decision. The Primary Agent sends the Finance-only user token together with its own service token, obtained through OAuth client credentials.

The Finance Agent validates both JWTs, requiring the expected audience and azp=primary-agent on the service token. This identifies the authorized OAuth client represented by the bearer token; it does not cryptographically bind the token to the calling process.

Exchanging Tokens for Downstream Access 🔗

The Finance Agent obtains an actor token using its own OAuth credentials, then requests an RFC 8693 token exchange with the user token as subject_token, its own token as actor_token, and audience=finance-api.

If Keycloak permits the exchange, it issues a token resembling this simplified JWT payload:

{
  "sub": "alice-subject-id",
  "act": {
    "client_id": "finance-agent",
    "sub": "finance-service-account-id"
  },
  "aud": "finance-api",
  "scope": "invoices.read invoices.write"
}

The sub identifies Alice; act identifies the Finance Agent acting on her behalf. The aud restricts the intended recipient, and scope declares the token’s capabilities—not blanket authorization to every invoice. Before calling the Finance API, the Finance Agent verifies that the exchanged token preserves Alice’s subject and names the expected actor.

The LLM does not create these credentials or determine either identity. Those guarantees come from OAuth and validation performed outside the model.

5. Enforcement: What Happens When an Agent Exceeds Its Privileges? 🔗

A valid delegated token is necessary, but it is not sufficient to authorize every operation.

The Finance API validates the token before applying its own business authorization policies. These include the token’s scopes, the user’s permissions, the agent’s permissions, and restrictions associated with the requested resource.

To demonstrate this behavior, the lab includes several scenarios using Alice, Bob, and a small financial database.

Scenario Result Reason
Alice reads an invoice from Finance ALLOW User and agent have the required permissions
Alice reads an invoice from HR DENY Department restriction
Alice asks the agent to approve a payment DENY Finance Agent lacks payments.approve
Bob attempts to read an invoice DENY Bob lacks the required invoice permission
Finance API receives a user token without delegation DENY Required delegated identity is missing
Finance API receives a token for the wrong audience DENY Token is not intended for this API

The Finance API also restricts the information returned to authorized callers. For example, invoice responses exclude the bank account field.

These controls are exercised by automated tests, including integration tests with real Keycloak-issued tokens and PostgreSQL. The full-chain security tests use deterministic tool selection rather than relying on LLM reasoning, allowing authorization outcomes to be verified independently.

Authorization is enforced outside the LLM: an agent can invoke a forbidden tool, but the Finance API can still deny the operation.

6. Threat Model, Security Guarantees and Limitations 🔗

An architecture should be evaluated not only by what it allows, but also by what happens when one of its components is compromised.

Consider a Finance Agent that has been compromised through a software vulnerability or malicious input.

An attacker controlling the agent may be able to access its OAuth credentials, intercept delegated tokens available to it, and invoke the Finance API directly.

However, compromising the agent does not automatically grant the attacker all the permissions of the users interacting with it.

The Finance API still enforces the agent’s permission ceiling, provided that the API, its authorization data, and Keycloak remain trusted. A compromised agent cannot gain payments.approve through its own delegated identity while the API’s policy denies that capability.

What the architecture does not prevent 🔗

Bearer token theft. A stolen, valid delegated token may be replayed by an attacker who can reach the intended service. The lab does not implement sender-constrained tokens or mutual TLS between the internal services.

Compromised agent credentials. An attacker possessing an agent’s OAuth credentials can authenticate as that client and request tokens from Keycloak. Delegation policies and the availability of valid user tokens constrain which exchanges can succeed, but a compromised agent remains a serious security event.

Token revocation. The Finance API validates JWTs locally. Revoking a delegation in Keycloak does not necessarily invalidate tokens that have already been issued. Short token lifetimes reduce the exposure window, while stronger revocation requirements would need additional mechanisms. Changes to the application’s own permission records, however, are reflected in subsequent authorization decisions.

Prompt injection. The architecture does not prevent an LLM from following malicious instructions embedded in retrieved content. It limits certain consequences by enforcing permissions outside the model. An attacker may still induce operations that fall within both the user’s and the agent’s authorized capabilities.

The implementation also makes deliberate proof-of-concept trade-offs. Internal service communication uses HTTP, Keycloak’s Client Delegation functionality is a Preview feature, and session and consent management are designed for a local, single-instance environment rather than a distributed production deployment.

Business authorization events are logged, but the audit implementation does not provide comprehensive, tamper-resistant evidence of every security-relevant action. Conversation content and authorized tool results may also be sent to the configured external LLM provider.

These limitations matter because least-privilege delegation reduces the potential impact of a compromise; it does not make a compromised agent trustworthy.

Production use would require a threat model suited to the data, environment, and business process.

7. From Proof of Concept to Enterprise Security Architecture 🔗

Scaling this pattern across an enterprise raises a question that technology alone cannot answer: who decides what an AI agent is allowed to do in a business system?

The team operating a Finance Agent should not be able to grant it payment approval or access to financial records unilaterally. A practical division of responsibilities is for the agent team to request capabilities, the application or business owner to approve them, and the IAM platform to manage agent identities and delegation mechanisms. Security defines common assurance requirements and reviews higher-risk use cases.

This division enables shared identity infrastructure without taking business authorization decisions away from the teams accountable for the data. It also requires clear ownership throughout an agent’s lifecycle: registration, access reviews, credential rotation, offboarding, and incident response.

The Cost of Stronger Security Boundaries 🔗

These controls have a price. Separate OAuth clients and destination-specific tokens increase configuration and maintenance. Consent introduces user friction, token exchange adds an authorization-server dependency, and resource APIs must correctly enforce delegated access.

For a low-risk assistant with no sensitive tools, that overhead may be excessive. For an agent that reads regulated data or modifies business records, the additional controls may be justified. The decision depends on the impact of misuse, the value of the agent’s capabilities, and the organization’s ability to operate those controls reliably.

This lab demonstrates a technical building block, not a complete enterprise governance model.

Final Thoughts 🔗

AI agents are distinct actors, not extensions of a user’s session. OAuth 2.0 delegation establishes who may act on whose behalf; the resource server determines what that combination can actually do. Neither removes the need to plan for stolen tokens, compromised agents, or malicious instructions.

The goal is not to make agents inherently trustworthy, but to ensure that the model’s behavior is never the sole control protecting a sensitive business operation.