How to Authorize AI Agents Using Token Exchange Open Standards

How to Authorize AI Agents Using Token Exchange Open Standards
Kim Maida
Kim Maida Developers
8m read

Your agents have too much access

You have a coding agent. It needs to call an API, so you generate a static API key, paste it into an .env file, and tell the agent to use that file whenever it needs API access.

Then you ask the agent to GET API data. It performs the read action and detects a data inconsistency, so it decides the next step is to POST a fix. The API key grants write permissions, so the agent tells you it found an error and now it’s helpfully fixing it. Suddenly you’re mashing the keyboard scrambling to interrupt the unexpected write tool call.

The API key has write access because it’s a kitchen sink: you do want the agent to write under specific conditions, but not for this task, and you forgot to explicitly tell it only to read. The agent is designed to be “helpful,” so it reads the data and uses its credential to do what it infers you want next.

The root problem isn’t just the overprivileged credential. It’s that there’s no boundary between the agent and the resource. Nothing checks whether this action, by this agent, this time, should be allowed. The agent has a credential, and it can use that credential without restriction or governance.

And the problem grows if the agent needs to use a second resource or your teammate needs agentic access. You end up with multiple .env files, multiple API keys, and no way to tell which agent used which key to do what. You don’t know which human either agent is acting on behalf of.

This is where most developers using and building agents are right now. Tool adoption has rapidly outpaced the auth story. Agents are making autonomous decisions about which APIs to call, but the credentials authorizing those calls are static, overprivileged, and untracked.

There’s a solution, and it doesn’t require throwing out everything we’ve built for identity and access management so far. Agents need ephemeral tokens scoped to the task and the resource. You need an authorization server that evaluates policy before issuing those tokens. If your policy forbids write access for a request, a write-scoped token never gets issued. There’s nothing to misuse because there’s no standing access.

So how can agents get the tokens they need?

An open standard called OAuth 2.0 Token Exchange (RFC 8693) has existed since 2020, and microservices teams have been using it quietly for years. Now, if you’re responsible for AI agents, token exchange just became directly relevant for you.

What are tokens?

If you’ve ever logged into an app that uses a third-party identity provider (Google, Okta, Auth0, etc.), you’ve used tokens. When you log in, the identity provider sends tokens to your app. Tokens are data artifacts that prove who you are (authentication with ID tokens) and what you’re allowed to do (authorization with access tokens). Your app presents an access token to an API, and the API verifies it before doing anything.

The authorization server is the set of endpoints that verify identity, determine appropriate permissions, and mint tokens. Traditional identity providers like Okta or Auth0 can fill this role. Keycard can be used with most identity providers. It’s a purpose-built platform for agent authorization, handling token exchange, agent identity, and access policies.

A token represents a specific entity’s authorization for a specific thing, and it expires. It’s not a skeleton key. The trouble starts when your agent needs authorization across multiple resources, each with different permission models. A single token doesn’t map cleanly to GitHub and Slack and Linear. Each resource has its own issuer, its own scopes, and its own intended recipient. The agent needs a separate token for each API.

Two patterns for authorizing agents

Agents operate in different modes. We’ll focus on two. The authorization model is different for each, and getting them confused causes real problems.

Delegated agents: human user in the loop

A developer tells their coding agent to read Linear issues, push a fix to GitHub, and post a summary to Slack. An employee asks a travel agent to check their calendar, book a flight, and submit an expense.

In these examples, a human initiated the task. The agent acts with that person’s authority, and the downstream resources need to verify that chain: this person authorized this agent to do this specific thing.

The agent can’t just forward the user’s login token. That token represents the user’s session with the agent app. It doesn’t grant access to resources like GitHub or Slack. And even if resources could accept that token, you’d be giving the agent the same level of access the user has, for as long as the token is valid. That doesn’t solve the .env problem: it’s just a different path to the same outcome.

Instead, the agent should take its proof of the user’s authorization and exchange it (through the authorization server) for an ephemeral, scoped token for each resource. The authorization server checks that the user authorized the agent to access that specific resource with those specific permissions, and issues a token that says so.

This way, the coding agent gets a GitHub token scoped to one repository. It gets a Slack token that can post to one channel. It gets a Linear token with read-only access. Each token is scoped to the task. When the task is done, the token is gone.

When there’s a human in the loop, some authorization is delegated from the human to the agent. This means the new token must carry both identities: the user and the agent. GitHub sees “the coding agent is pushing code on behalf of Kim.” This is what you need for accountability.

In a delegated token, the chain of authority shows up in the token itself as an act (actor) claim. Here’s what a decoded JSON Web Token (JWT) with delegation looks like:

{
  "sub": "[email protected]", // human who authorized the action
  "aud": "https://api.linear.app", // specific resource this token is for
  "scope": "read", // permission boundary: no access beyond this
  "exp": 1767225600, // when this token becomes invalid
  "act": { // delegation chain
    "sub": "company-agent-id4825c8w" // agent executing the action
  }
}

Subject sub is Kim (the person who authorized the action). Actor act is the coding agent (the app performing the action). Scope scope is limited to read. Expiration exp is a timestamp indicating when this token becomes invalid. When something goes wrong, the audit log shows exactly who authorized it, which agent did it, what permissions it had, and when those permissions expired.

The travel agent scenario works the same way. The expense API sees “the travel agent submitted an expense on behalf of Kim, scoped to domestic flights, authorized at 2:15pm.” If the booking is wrong, you can trace the full chain.

Autonomous agents: no human identity

Not every agent has access delegated by an authenticated human user. A chatbot on your homepage answers anonymous product questions, queries a knowledge base, and logs call bookings to a CRM. It runs continuously. Nobody logged in and said “do these things because Kim authorized it.”

The same problems apply: the chatbot shouldn’t hold permanent, broad credentials. It should get scoped, ephemeral tokens for the specific resources it needs. You should be able to see what it did and when.

The difference is how the agent authenticates. With a delegated agent, the user’s identity is the origin point. With an autonomous agent, the agent authenticates as itself from the start.

That might mean client credentials: the agent has a client ID and client secret registered with the authorization server, like how you’d register any traditional application using OAuth. This is the most familiar model. The agent presents its credentials, the authorization server validates them, then issues scoped tokens for the resources the agent needs.

Or it might mean workload identity: instead of storing a secret, the agent proves it’s running where it claims to be running. AWS, GCP, and other cloud providers can attest that a workload is running on a specific instance or service. The identity is bound to the runtime environment. There’s no secret to rotate or leak.

The standard: OAuth 2.0 Token Exchange (RFC 8693)

Both delegated and autonomous agents can use the same underlying specification. RFC 8693 defines a grant type called “token exchange.” The agent already has a token, either from the user authorizing the agent (delegated) or from the agent’s authentication (autonomous). The agent sends this token to the authorization server and exchanges it for an access token for a specific resource.

Token exchange sequence: User delegates to AI Agent, agent requests scoped token from Keycard, Keycard validates and issues ephemeral access token, agent calls Resource API directly

To initiate a token exchange, the client (the agentic application in this case) sends a POST request to the authorization server’s token endpoint:

POST /oauth/2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=eyJhbGciOiJFUzI1NiIs...
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&resource=https://api.resource.com
&scope=read

The agent authenticates this request with its own credentials so the authorization server knows which agent is making the exchange. The subject_token is the token being exchanged. The resource parameter identifies the target service for the new token, and scope defines the permissions needed for the task.

For delegation, the authorization server needs to identify the agent. You can pass the agent’s identity explicitly with the optional actor_token parameter. Alternatively, many implementations derive the agent’s identity from client authentication as described above. The agent’s identity fills in the delegation chain audit trail, showing which agent is acting on behalf of which user.

Note: An optional audience request parameter can be used alongside resource to indicate multiple target services with a mix of logical names and resource URIs.

If the exchange succeeds, the authorization server returns a new token. This token is scoped to the specified resource and task, and only valid for a limited time:

{
  "access_token": "eyJhbGciOiJFUzI1NiIs...",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read"
}

The agent then authorizes the resource call with the token, performs its task, and discards the token.

Right now, agents are making RFC 8693 necessary on a much broader scale. When a traditional app logs in, one OAuth flow at the front door is usually enough. But agents decide which APIs to call at runtime, and they need scoped credentials for each resource. Token exchange is how you get the right credential for the right resource at the right time, without storing long-lived secrets or giving agents more access than they need.

Token exchange security for agent authorization

There are some things RFC 8693 does not cover. Token lifecycle, policy, governance, and identity infrastructure are all left to the discretion of the implementer. The spec defines the token exchange mechanism, but it doesn’t tell you how to manage which agents can access which resources or how to enforce least-privilege policies. So what guardrails do you need when the client can think for itself?

Authenticate the agent. The agent must prove its identity. “Kim’s token, forwarded by some process” is not an identity. “company-agent-id4825c8w, acting on behalf of Kim” is.

Scope to the task. An agent with too much access will do things you didn’t intend. Agents make decisions at runtime, and they’ll use any scopes available to get the job done. Request only the permissions the task actually requires.

Enforce policy at issuance. The authorization server should perform governance before issuing tokens. If policy only allows read-only, the write token is never created. The decision about what the agent can access is up to you, not the agent.

Don’t store credentials. Token exchange gives your agent a fresh token for each tool call. Use the token once and don’t store it. This way, “helpful” agents don’t have overprivileged tokens they can reuse across different tool calls to do more than you asked for.

Record the full delegation chain. User, agent, resource, action, and timestamp. If something goes wrong six weeks from now, the answer to “what happened” will be in the logs, not in someone’s memory (or the agent’s memory.md).

What to do next

If you’re still pasting static secrets into .env files, you’re not behind. Most people using and building agents are doing the same. But now you know a better way: scoped access, governance before issuance, ephemeral tokens, traceable delegation chains.

If you don’t want to build your own authorization server and implement RFC 8693 and policy governance manually (and let’s face it, you really shouldn’t roll your own auth), check out how we’re doing it at Keycard.

If you’re a developer using agents, you can use token exchange in the command line to govern coding agents like Claude Code and Codex.

If you’re building agents, take a look at the Keycard Build a Slack agent guide or explore a client SDK.

Securing agentic access doesn’t have to be hard. The standards exist, the tooling exists, and platforms like Keycard exist so you don’t have to build any of it from scratch.

Last updated May 6, 2026

Have questions about agent security?

Ask our agent — it's a live Keycard-on-Keycard demo.