SecurityIntegrationsAIApexSalesforce
Your Salesforce Org Needs a Bouncer: How OAuth Token Exchange Checks IDs at the Door

In a previous post on the Tython blog, we covered the JWT bearer flow as a zero-trust handshake–a strong pattern for server-to-server authentication that eliminates stored passwords and interactive logins. That’s still true. But the external system still holds a private key capable of signing JWTs that your org will honor. If that key is compromised, the attacker doesn’t need a password or a session–they can mint their own tokens. The question becomes: is there a model where the external system doesn’t hold Salesforce-specific signing material at all?
There’s a pattern that changes this model fundamentally: the OAuth 2.0 Token Exchange flow (RFC 8693). Salesforce supports it, but most orgs haven’t adopted it–and they should, especially as agentic AI workflows start chaining Salesforce into broader multi-system processes.
Scott Covert gave a talk on this topic at Dreamin’ in Data 2026 in Chicago, and the audience reaction confirmed what we suspected: most Salesforce teams haven’t heard of token exchange, and once they see how it works, the security benefits are immediately obvious.
The Bouncer Analogy
Think of your Salesforce org as a bar. In a traditional OAuth flow, every vendor gets a copy of the key to the back door. They let themselves in whenever they want, help themselves to whatever’s behind the counter, and you have no one checking who they are at the point of entry. If a key gets copied, anyone can walk in.
Token exchange works like putting a bouncer at the front door. Vendors don’t get a key–they show up with an ID issued by a trusted authority. Your bouncer (a custom Apex Token Exchange Handler) checks that ID against the issuer’s credentials, confirms it’s legitimate, and stamps their hand based on what they’re allowed to access. A 21-year-old gets the full-bar stamp. An 18-year-old gets the no-alcohol stamp. Someone with a fake ID gets turned away at the door.
That’s token exchange. The IdP (Okta, Auth0, Entra ID) is the trusted authority that issues the ID–a signed JWT attesting to the running user’s identity. The external service carries that JWT to Salesforce’s front door. On the Salesforce side, a custom Apex Token Exchange Handler acts as the bouncer: it verifies the JWT against the IdP’s public key, resolves the user’s identity to a matching Salesforce user (or creates one if configured), and returns a Salesforce access token scoped to that user’s permissions–the hand stamp. The external service doesn’t decide who it gets to be in Salesforce or what access it receives. The IdP’s identity assertion determines who, and Salesforce’s handler determines what.
How It Works in Salesforce
The flow has three participants:
- The external service (the patron) that needs Salesforce access. It carries a signed JWT from the IdP that asserts the running user’s identity–the ID card.
- The identity provider (the trusted authority)–Okta, Auth0, Entra ID, or another identity platform. It verifies the user’s identity and issues the signed JWT that the external service carries to Salesforce.
- Salesforce (the bar, with a bouncer at the door) is configured with a Connected App or External Client App and an Apex Token Exchange Handler that validates the IdP’s tokens and determines what access to grant.
The sequence:
- A user (or service identity) authenticates with the IdP. The IdP verifies their identity and issues a signed JWT containing identity claims about that user.
- The external service carries that JWT directly to Salesforce’s token endpoint, along with the consumer key and secret from the Connected App or External Client App.
- Salesforce invokes the Apex Token Exchange Handler, which validates the JWT signature against the IdP’s public key, extracts the identity claims, finds or provisions a matching Salesforce user, and returns a Salesforce access token for that user.
The key security properties:
- The external service holds a consumer key and secret from the Connected App or External Client App, but those alone aren’t enough. Salesforce validates both the client credentials and the IdP-issued JWT. The external service cannot forge access on its own–it needs a legitimately signed token from the IdP.
- The IdP controls whose identity gets attested. If a user or service is disabled in the IdP, no valid JWT is issued, and Salesforce will have nothing to honor.
- The Apex handler is a programmable policy enforcement point. It can apply custom logic: restrict access based on claims in the JWT, enforce conditional rules, map to different Salesforce users based on context, or reject the exchange entirely.
- Token lifetimes are short and scoped. The Salesforce access token returned by the handler can be tightly constrained in both duration and permissions.
Why This Matters for Agentic Workflows
This is where token exchange shifts from a nice-to-have to a near-requirement.
Agentic AI workflows–where an AI agent orchestrates actions across multiple systems–are rapidly moving from demos to production. In these workflows, Salesforce isn’t the center of the universe. It’s one data source in a chain. An agent might query a data warehouse, enrich data from a third-party API, update a record in Salesforce, then trigger an action in a billing system.
In a traditional model, that agent holds separate credentials for every system in the chain. If the agent is compromised, every system is compromised. Token exchange breaks this pattern. The agent authenticates once with the IdP, and each downstream system–Salesforce included–independently validates the IdP-issued token through its own exchange handler. The IdP remains the single source of truth for the agent’s identity, and each system applies its own authorization logic at the point of access.
This matters because agentic workflows amplify the blast radius of credential theft. A compromised API key to a single-purpose integration is bad. A compromised credential for an agent that has access to your CRM, your data warehouse, and your billing system is catastrophic. Token exchange keeps each link in the chain independently verifiable and revocable–and keeps Salesforce-specific credentials out of the agent’s hands entirely.
What to Do Now
- If your org uses external integrations that authenticate to Salesforce via stored credentials or long-lived tokens, evaluate whether token exchange can replace them. The strongest candidates are integrations where the external system already authenticates through an IdP you control.
- If you’re building or evaluating agentic AI workflows that include Salesforce, design the authentication layer around token exchange from the start. Retrofitting it later is harder.
- If you use Okta, Auth0, or Entra ID, check whether your current tier supports issuing tokens that can be consumed by external systems. The Salesforce side requires a Connected App or External Client App and a custom Apex class extending the
Auth.Oauth2TokenExchangeHandlerabstract class. - Review your Connected App and External Client App configurations in Setup. Understand which external systems have direct OAuth access to your org and whether an IdP-mediated token exchange could reduce that direct exposure.
Deeper Dive
The Token Exchange Flow, Step by Step
Understanding the full sequence makes it easier to see where security decisions are enforced and where the flow differs from a standard JWT bearer grant.
Step 1: The IdP Verifies Identity and Issues a Signed JWT
A user or service identity authenticates with the identity provider (Okta, Auth0, Entra ID, etc.) using whatever mechanism the IdP supports: an authorization code grant, client credentials, a service principal. Upon successful authentication, the IdP issues a signed JWT. This JWT is an identity assertion–it contains claims about who the user is, what attributes or group memberships they have, and when the token expires–and is signed with the IdP’s private key.
This JWT isn’t issued specifically for Salesforce. It’s a general-purpose identity assertion from the IdP. The same token could potentially be used to authenticate to any system that trusts the IdP. The external service is simply the carrier–it receives the JWT and will present it to Salesforce on the user’s behalf.
Step 2: The External Service Presents the JWT to Salesforce
The external service sends a token request directly to Salesforce’s OAuth token endpoint. The request follows RFC 8693 and includes:
grant_type:urn:ietf:params:oauth:grant-type:token-exchangeclient_id: The consumer key from the Connected App or External Client Appclient_secret: The corresponding consumer secretsubject_token: The IdP-signed JWTsubject_token_type:urn:ietf:params:oauth:token-type:jwt
Salesforce authenticates the client using the consumer key and secret, then routes the token exchange request to the handler configured for that Connected App or External Client App.
Step 3: The Apex Token Exchange Handler Validates and Resolves
This is the critical step–the bouncer checking the ID and deciding what stamp to give. Salesforce invokes a custom Apex class that extends the Auth.Oauth2TokenExchangeHandler abstract class. This handler:
- Extracts the JWT from the request
- Validates the signature against the IdP’s public key (typically retrieved from the IdP’s JWKS endpoint or a stored certificate)
- Checks standard JWT claims: expiration (
exp), issuer (iss), audience (aud) - Extracts identity claims–typically a subject identifier, email, or custom claims that map to a Salesforce user
- Looks up a matching Salesforce user. If no match exists and the handler is configured to allow it, it can provision a new user on the fly–setting profile, permission sets, and field values based on the JWT claims
- Returns a Salesforce access token scoped to that user’s permissions
If any validation step fails–bad signature, expired token, unrecognized issuer, no matching user and provisioning isn’t allowed–the handler rejects the exchange and returns an error. The external service never gets a Salesforce access token.
Step 4: The External Service Uses the Salesforce Access Token
With a valid access token in hand, the external service makes API calls to Salesforce just like any other OAuth client. The token is scoped to the Salesforce user the handler resolved, and all operations are logged under that user’s identity in Salesforce’s event logs and audit trail. When the Salesforce access token expires, the external service presents the IdP-issued JWT to Salesforce again for a new exchange. If the JWT itself has also expired, the external service needs to obtain a fresh one from the IdP first.
The Apex Handler: Your Programmable Bouncer
The Auth.Oauth2TokenExchangeHandler abstract class is what makes Salesforce’s implementation of token exchange distinctive. Unlike a standard OAuth flow where Salesforce validates a token against static configuration, the handler gives you a programmable validation and user-resolution layer.
What the Handler Controls
The handler has full control over:
- Token validation logic. You decide how to verify the JWT. You can call the IdP’s JWKS endpoint to fetch the current signing keys, or use a stored certificate. You can enforce custom claim requirements beyond the standard JWT fields–for example, requiring a specific
scopeorroleclaim. - User resolution. You define how to map the JWT’s identity claims to a Salesforce user. This might be a simple email-address lookup, or it might involve matching on a custom external ID field, a federation identifier, or a combination of claims.
- User provisioning. If no matching user exists, the handler can create one. You control which profile and permission sets the new user gets, what field values are populated, and under what conditions provisioning is allowed versus rejected. This is particularly relevant for partner and vendor integrations where users may not exist in your org ahead of time.
- Rejection logic. The handler can reject the exchange for any reason. You can block specific issuers, enforce IP-based restrictions using the JWT’s claims, require specific audience values, or implement rate-limiting logic. This is the custom policy enforcement layer that doesn’t exist in a standard JWT bearer flow.
A Simplified Example
public class ExternalIdPTokenHandler extends Auth.Oauth2TokenExchangeHandler {
// Validate the IdP-signed JWT (signature, expiration, issuer, audience)
public override Auth.TokenValidationResult validateIncomingToken(
String appDeveloperName,
Auth.IntegratingAppType appType,
String incomingToken,
Auth.OAuth2TokenExchangeType tokenType
) {
Map<String, Object> claims = decodeAndVerifyJwt(incomingToken);
// Reject if the issuer isn't the expected IdP
if (claims.get('iss') != 'https://your-idp.example.com') {
throw new Auth.Oauth2TokenExchangeException('Unrecognized issuer');
}
// Additional validation: check expiration, audience, custom claims
// Build the UserData from the JWT claims
String email = (String) claims.get('email');
String firstName = (String) claims.get('given_name');
String lastName = (String) claims.get('family_name');
String sub = (String) claims.get('sub');
String provider = (String) claims.get('iss');
Auth.UserData userData = new Auth.UserData(
null, // identifier
firstName, // firstName
lastName, // lastName
null, // fullName
email, // email
null, // link
sub, // username
null, // locale
provider, // provider
null, // siteLoginUrl
null // attributeMap
);
return new Auth.TokenValidationResult(true, null, userData, incomingToken, tokenType, null);
}
// Resolve the token's subject to a Salesforce user
public override User getUserForTokenSubject(
Id networkId,
Auth.TokenValidationResult result,
Boolean canCreateUser,
String appDeveloperName,
Auth.IntegratingAppType appType
) {
// Use the validation result to look up a matching Salesforce user
Auth.UserData userData = result.userData;
String email = userData.email;
List<User> users = [SELECT Id FROM User WHERE Email = :email AND IsActive = true LIMIT 1];
if (users.isEmpty()) {
if (canCreateUser) {
// Provision a new user based on the token's claims
}
throw new Auth.Oauth2TokenExchangeException('No matching user found');
}
return users[0];
}
}
The actual implementation would include robust JWT validation (signature verification against JWKS, claim checks, error handling), but the structure illustrates the point: you control every decision in the exchange.
Token Exchange vs. JWT Bearer: Where the Security Difference Lives
On the surface, token exchange and JWT bearer flows both end with the external service holding a Salesforce access token. The difference is in what the external service needs to know and hold in order to get there.
JWT Bearer Flow
In a standard JWT bearer flow, the external service creates and signs the JWT itself, using a private key that corresponds to a certificate uploaded to a Salesforce Connected App or External Client App. The external service needs:
- The consumer key
- A private key for signing JWTs
- Knowledge of the Salesforce user to act on behalf of
With these three pieces, the external service can mint valid JWTs and obtain Salesforce access tokens any time it wants, with no intermediate validation. If the private key is compromised, the attacker has direct, unmediated access to the org until the certificate is revoked in Salesforce.
Token Exchange Flow
In the token exchange model, the external service still needs the consumer key and secret–but it no longer needs a private signing key or knowledge of which Salesforce user to act on behalf of. It needs:
- The consumer key and secret from the Connected App or External Client App
- Credentials for its own IdP (Okta, Auth0, Entra ID, etc.)
- The ability to reach Salesforce’s token endpoint
The consumer key and secret alone are not enough to obtain access. Salesforce validates both the client credentials and the IdP-signed JWT. The signing key for the JWT lives at the IdP, not at the external service. The external service cannot forge an identity assertion that Salesforce will accept–it can only carry one that the IdP legitimately issued for an authenticated user. This means:
- Revoking access is centralized. Disable a user or service in the IdP, and no valid JWT can be issued for that identity. Salesforce will have nothing to honor. You don’t need to rotate certificates in Salesforce.
- Key management is simplified. The IdP manages its own signing keys. Salesforce trusts the IdP’s public key. The external service holds neither.
- Validation is programmable. The Apex handler can enforce custom rules that go beyond what standard Connected App or External Client App configuration supports–claim-based access control, dynamic user provisioning, contextual rejection.
- The blast radius of compromise is smaller. A compromised external service can replay its current JWT (until it expires), but it cannot mint new ones. And the Apex handler can be updated to block the compromised service’s tokens immediately.
The Tradeoff
Token exchange requires writing and maintaining an Apex handler. It’s more work upfront than uploading a certificate and configuring a Connected App or External Client App. But for orgs managing multiple external integrations–and especially for orgs adopting agentic workflows–the centralized control and reduced credential exposure justify the investment.
Designing Agentic Workflows with Token Exchange
The agentic AI use case deserves specific architectural guidance because the authentication pattern differs from traditional service-to-service integration.
The Problem with Static Credentials in Agentic Flows
In a traditional integration, Service A calls Service B. The credential relationship is static and known at deployment time. You configure it once, and it runs.
Agentic workflows are dynamic. An AI agent decides at runtime which services to call, in what order, and with what parameters. The agent might:
- Query a vector database for relevant context
- Call Salesforce to retrieve account data
- Call a billing API to check payment status
- Update Salesforce with an enriched record
- Send a notification through a messaging service
If the agent holds static credentials for all five systems, it has a permanent, broad attack surface. A single compromise exposes everything. And because the agent’s behavior is non-deterministic (it decides which services to call based on context), you can’t predict which credentials will be in memory at any given time.
Token Exchange as the Authentication Backbone
Design the agentic workflow so the agent authenticates to the IdP once (using its own service identity), and then uses the IdP-issued JWT to authenticate to each downstream system that supports token exchange:
- The user (or the agent’s service identity) authenticates to Okta/Auth0/Entra ID and receives a signed JWT asserting their identity.
- When the agent needs to call Salesforce, it presents the JWT to Salesforce’s token endpoint. The Apex Token Exchange Handler validates it, resolves a Salesforce user, and returns an access token. The handler can enforce agent-specific policies: read-only access, restricted object visibility, limited API call volume.
- When the agent needs to call a billing API, it presents the same IdP-issued JWT (or requests a new one scoped to the billing service). The billing system performs its own validation and applies its own authorization rules.
- Each downstream access token expires independently. When the Salesforce token expires, the agent presents a fresh JWT from the IdP, and the Apex handler re-evaluates at that moment–checking whether the agent’s access has been revoked, whether policies have changed, or whether new restrictions apply.
This architecture means:
- The agent never holds system-specific credentials for any downstream service. It holds one IdP-issued JWT.
- Each system interaction is independently authorized and auditable.
- Revoking the agent’s IdP identity kills access to all downstream systems instantly.
- You can enforce different permission levels for the same agent across different systems–read-only in Salesforce, write access in the billing system, no access to HR data–all through each system’s own token exchange logic.
Audit and Observability
Token exchange creates natural audit trails at two levels. At the IdP, every JWT issuance is logged: which service authenticated, when, and what claims were included. At Salesforce, every exchange is processed through the Apex handler, which can log the inbound JWT’s claims, the resolved user, and the outcome (success or rejection). All API operations performed with the resulting access token appear in Salesforce’s event logs under the resolved user, just as they would with any other OAuth flow.
For agentic workflows, this dual-layer logging gives security teams visibility into both the agent’s identity assertions (from the IdP) and its actual Salesforce operations (from the event logs)–without having to correlate across disparate systems.
The Bottom Line
Token exchange isn’t a new protocol. RFC 8693 was published in 2020, and the IdPs that most Salesforce orgs already use support it. What’s new is the urgency. As agentic AI workflows move from prototypes to production, the credential model that underpins multi-system orchestration becomes a critical security decision. Letting every agent and every integration hold direct Salesforce credentials is the equivalent of handing out copies of the key to the back door. It works until it doesn’t–and when it fails, the blast radius is the entire org.
The bouncer model–centralizing identity at the IdP, validating at Salesforce’s front door through a programmable Apex handler, stamping access based on verified identity–is how production-grade agentic architectures should authenticate to Salesforce. The tooling exists today. The question is whether your org adopts it before the next wave of integrations makes the credential sprawl unmanageable.
Book a 15-Minute Security Strategy Call