All insights

Agentforce

OAuth token exchange: letting an AI agent act as the user, not the org

An external AI agent or MCP client needs to do something in your Salesforce org. The lazy answer is a stored integration-user password or a client-credentials token that is the whole org. Both hand a non-human a standing, blanket identity. OAuth 2.0 token exchange is the fix: the agent presents the end user's token and gets back a short-lived Salesforce token scoped to that user. Here is the flow, the Apex handler, and why the audit trail should name a person.

OAuth token exchange: letting an AI agent act as the user, not the org, article illustration

An AI agent that lives outside Salesforce eventually needs to do something inside your org. An MCP client in a chat app, a workflow in another cloud, a copilot your platform team built: read an account, log a case, update an opportunity. The question nobody enjoys answering is which identity it uses to do it.

The two easy answers are both wrong for an agent. Stand up an integration user and store its password, and now a non-human holds a standing credential that can see everything that user can, with no tie to whoever asked.

Use the client-credentials flow instead, and every request arrives as the same service identity, so the audit log says “the integration ran this,” never “Dana asked for this.” Either way you have handed a machine a blanket, permanent identity and thrown away the one fact that matters when something breaks: who.

OAuth 2.0 token exchange fixes exactly that. The agent presents the end user’s own token, and Salesforce hands back a short-lived access token scoped to that user. The agent acts as the person, with the person’s permissions, and the audit trail names them.

Salesforce has supported this flow, defined in RFC 8693, for a while now. The rise of external agents is what finally makes it worth wiring up.

The flow, in one exchange

Token exchange is one call to the token endpoint with a grant type most people have never used. The agent already holds a token proving who the user is, issued by your identity provider, Okta, Entra ID, Auth0, or Salesforce itself. It sends that token to Salesforce and asks for a Salesforce token in return.

POST https://login.salesforce.com/services/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<the end user's token from your IdP>
&subject_token_type=urn:ietf:params:oauth:token-type:jwt
&client_id=<consumer key of the External Client App>
&client_secret=<the app's consumer secret>
&scope=api

Two tokens do two jobs. The subject_token says who the user is. The client credentials, sent as the consumer key plus its secret, say which app is asking.

Salesforce authenticates the app first, then validates the user’s token and decides which Salesforce user it maps to. What comes back is an ordinary Salesforce access token, short-lived and scoped, that the agent uses for a few minutes and then discards.

For a genuine on-behalf-of chain, the spec also defines an optional actor_token, the identity of the party doing the acting on top of the subject. Use it to express “this agent, acting for this user,” rather than collapsing both into one credential. Most first deployments start without it and add it when the delegation chain needs to be explicit in the token.

The handler is where you decide who gets in

Salesforce does not guess which user a foreign token represents. You tell it, in Apex, with a token exchange handler. The handler is the security boundary of the whole flow, and it is the piece you own.

You write a class that extends Auth.Oauth2TokenExchangeHandler and override validateIncomingToken. Salesforce calls it with the inbound token and expects back an Auth.TokenValidationResult that says whether the token is valid and, if so, which Salesforce user it belongs to.

global class ExternalIdpTokenExchangeHandler extends Auth.Oauth2TokenExchangeHandler {

    // Salesforce invokes this with the inbound subject_token and request context.
    // Confirm the exact parameter signature against the current Apex Reference for
    // Auth.Oauth2TokenExchangeHandler before you compile.
    global override Auth.TokenValidationResult validateIncomingToken(/* inbound token + context */) {

        // 1. Verify the token is authentic: check its signature against the IdP's
        //    JWKS, and confirm the issuer, audience, and expiry. Reject on any failure.

        // 2. Resolve the token's subject to ONE active Salesforce user, never a
        //    shared integration user. Match a claim to FederationIdentifier or a
        //    verified email, and build an Auth.UserData for that user.

        // 3. Return a valid Auth.TokenValidationResult carrying that user, so the
        //    issued Salesforce token runs as them. Return an invalid result to deny.
        return new Auth.TokenValidationResult(/* valid, userData, token, token type */);
    }
}

Two rules keep this handler safe. Verify the token properly, signature, issuer, audience, and expiry, because a handler that trusts an unverified token is an open door with a lock painted on it.

And map to a real, specific, active user, so the permissions the agent inherits are that user’s, governed by the same CRUD, FLS, and sharing you already maintain. A handler that maps everyone to one high-privilege account has recreated the integration-user problem with extra steps.

Token exchange does not grant new power. It borrows the user’s power for a few minutes, and writes their name on everything it does.

Why this is the right shape for AI agents

An agent is not a person and not quite an app, and that is what breaks the older patterns. It acts continuously, on behalf of many different users, often from outside your trust boundary. Three properties of token exchange line up with that.

It preserves identity. Because the agent inherits the user’s permissions rather than a service account’s, an agent asking about Dana’s cases sees what Dana can see and nothing more. The permission model you already run does the enforcement, instead of a second, hand-maintained profile on an integration user drifting out of sync.

It stays short-lived. The issued token expires quickly, so a leaked agent token is a small blast radius, not a permanent key to the org. Compare that to a stored integration password, which is valid until someone remembers to rotate it.

It keeps the audit trail truthful. Every record the agent touches carries the mapped user as the actor. When compliance asks who updated the opportunity, the answer is a named person who authorized the action, not “the AI.” That is the difference between an agent you can put in front of a regulator and one you can’t.

This mirrors a pattern you may already use outbound. When an Agentforce agent calls an external API, you reach for Named Credentials so the platform holds the secret. Token exchange is the same instinct pointed the other way: when an external agent calls into Salesforce, it should carry a delegated, scoped identity, not a shared password.

Where it fits, and where it doesn’t

Token exchange earns its place when a caller acts for a specific human. An MCP client that a rep drives from their desktop, a partner portal agent acting for the signed-in partner, a cross-cloud workflow carrying the originating user forward. In each, there is a real person whose authority the agent should borrow, and whose name belongs on the result.

The flow is the wrong tool when there is no user to represent. A nightly batch that reconciles two systems is a service acting on its own behalf, and client credentials are the honest choice there, precisely because no human authorized any single row. Do not manufacture a fake user to force token exchange onto a workload that has none.

One caveat worth surfacing before you design around it. The exact handler signatures, the External Client App settings, and the enablement toggles move between releases and are thinly documented, so confirm the current setup steps and the Auth class members in the live Apex Reference for your API version rather than trusting a blog, this one included, on the fine print. The architecture is stable. The configuration details are the part to verify against your own org.

What I would set up

Register an External Client App for the calling agent and enable it for the token exchange flow, so Salesforce will accept the grant from that client. Write the handler next, and spend your time on step one of it, real token verification against your IdP’s keys, because that check is the entire security of the flow.

Map subjects to real users through a durable claim, a FederationIdentifier you control, not a display name that can change. Scope the issued tokens tightly and keep their lifetime short. Then test the failure paths first: an expired token, a token from the wrong issuer, a subject with no matching active user should each be denied cleanly, before you ever test the happy path.

Get that right and you can let external agents into your org without flinching, because each one arrives as a named person, borrowing that person’s access for a few minutes, leaving that person’s name on the work. The alternative, a shared password on a non-human that can do anything forever, is the thing your next security review is going to ask about anyway.

Understanding the basics

What is OAuth 2.0 token exchange in Salesforce?

Token exchange is an OAuth flow, defined in RFC 8693, that swaps one security token for another. An external app or AI agent presents a token proving who the end user is, and Salesforce returns a short-lived Salesforce access token scoped to that user. It lets a caller act on behalf of a specific person instead of using a shared integration user or a client-credentials token that carries the same blanket identity on every request.

How is this different from the client-credentials flow?

Client credentials give every request the same service identity, with no connection to the human who triggered it, which is right for a pure system-to-system job like a nightly batch. Token exchange preserves the end user’s identity, so the agent inherits that user’s permissions and the audit trail names them. Use client credentials when no person authorized the action, and token exchange when one did.

What is a token exchange handler?

A token exchange handler is an Apex class you write that extends Auth.Oauth2TokenExchangeHandler and overrides validateIncomingToken. Salesforce calls it during the flow to check that the inbound token is authentic, verifying its signature, issuer, audience, and expiry, and to map the token’s subject to a specific Salesforce user. The handler is the security boundary of the flow, so verifying the token properly and mapping to a real, least-privilege user are the two things it must get right.

Should AI agents use a shared integration user instead?

Only for workloads that act on their own behalf with no human involved. For an agent acting for a person, a shared integration user is a standing, high-privilege credential on a non-human, with an audit trail that hides who asked. Token exchange gives each request the asking user’s scoped identity and a short-lived token, which is both safer and far easier to defend in a security review.


Wiring external agents or MCP clients into Salesforce and unsure how they should authenticate? Getting delegated, least-privilege identity right, so an agent can act without becoming a standing risk, is exactly the integration and security work we do. Talk to us.

Keep reading

All insights