DEV Community

Devam Gupta
Devam Gupta

Posted on • Originally published at Medium

Salesforce ECA Security Compliance for AppExchange ISVs: A Technical Breakdown of the Four OAuth Controls

If you received Salesforce’s mandatory security email in late April 2026 and immediately started questioning which of the four OAuth controls actually apply to your AppExchange application — you are not alone.

The email landed without much context. The official documentation is written for a broad audience that includes everything from simple connected apps to complex multi-flow enterprise integrations. And the ISV partner community Slack channels filled up with the same question repeated across dozens of threads: does this apply to us, and if so, what exactly do we need to change ?

I spent two weeks working through this for an enterprise AppExchange application I architect — one that uses an External Client App (ECA) registered on an internal Salesforce org for license management. The architecture is server-to-server, the OAuth flow is JWT Bearer Grant, and refresh tokens are never issued or stored.

What I found is that the answer is highly dependent on your specific OAuth architecture. For some ISVs, all four controls require code changes. For others — particularly those using JWT Bearer flows — most of the controls are either non-events or outright exempt. The challenge is knowing which category you are in and why.

This article walks through each of the four requirements with enough technical depth to make that determination for your own application.

The Requirement — What Salesforce Mandated
Salesforce issued a mandatory directive requiring all AppExchange ISV partners to implement four OAuth security controls on all Connected Apps and External Client Apps by May 11, 2026.

The applicability scope, per Salesforce:

“Any Connected App or External Client App that is included or used in connection with a Partner Application, provided or created by the Partner, and in use by more than two customer production orgs.”

The consequence of non-compliance:

“AppExchange de-listing and/or Salesforce’s temporary or permanent suspension of the Partner Application’s interoperation with Salesforce’s services.”

The four controls are:

  1. OAuth PKCE (Proof Key for Code Exchange)
  2. Refresh Token Rotation (RTR)
  3. Idle Refresh Token TTL — 30 Days
  4. Refresh Token IP Range Allowlist

Before evaluating each one, you need to understand your own OAuth architecture clearly. The analysis below assumes two distinct ISV patterns — I will contrast them throughout.

**

Two Common ISV OAuth Patterns

Pattern A — Server-to-Server JWT Bearer (no user-facing OAuth)

The ISV application uses the OAuth 2.0 JWT Bearer Grant flow to authenticate server-to-server. A pre-authorized integration user is configured on the target org. The application generates a signed JWT assertion, posts it to the Salesforce token endpoint, and receives a short-lived access token. No refresh tokens are issued or stored. No user interaction is required for authentication.

This pattern is common for license management systems, data sync integrations, and backend telemetry where a human user is not part of the authentication loop.

Pattern B — Authorization Code Flow with Refresh Tokens (user-facing OAuth)

The ISV application implements the standard OAuth 2.0 Authorization Code flow. A user authenticates through a Salesforce login page, the application receives an authorization code, exchanges it for an access token and a refresh token, and stores the refresh token for subsequent silent re-authentication. This is the standard pattern for user-facing connected applications.

Most of the four requirements were designed primarily for Pattern B. Understanding this distinction is what allows you to correctly scope your compliance work.

Diagram 1 — JWT Bearer Flow vs Authorization Code Flow

Requirement 1 — PKCE (Proof Key for Code Exchange)

What it is:
PKCE is a security extension for the Authorization Code flow that prevents authorization code interception attacks. Before initiating the auth flow, the client generates a cryptographically random code_verifier, hashes it to produce a code_challenge, includes the challenge in the authorization request, and presents the original verifier when exchanging the code for tokens. A stolen authorization code is useless without the matching verifier.

Who it was designed for:
Public clients — primarily mobile applications and single-page apps — where storing a client secret securely is not possible. However, Salesforce now mandates PKCE for all ECAs regardless of client type.

Impact on Pattern A (JWT Bearer):
Zero. PKCE is an extension to the Authorization Code flow. JWT Bearer does not use authorization codes, does not have a redirect step, and does not receive an authorization code to intercept. Enabling the PKCE checkbox on the ECA satisfies the requirement but changes absolutely nothing in the JWT Bearer authentication path. No code changes are needed.

Impact on Pattern B (Auth Code):
Requires code changes. Your application must generate a code_verifier/code_challenge pair on each auth initiation and include the verifier in the token exchange request. Most modern OAuth libraries support PKCE natively — check your library’s documentation for the relevant parameters.

“Although JWT flow is not impacted by PKCE, salesforce strongly recommends turning on the controls. For JWT bearer flows, there is no code change needed for PKCE.”

Action:
Enable the “Require Proof Key for Code Exchange (PKCE) extension for Supported Authorization Flows” checkbox in your ECA’s OAuth settings. That is the entire change for JWT Bearer applications.

Requirement 2 — Refresh Token Rotation (RTR)

What it is:
When RTR is enabled, every time a client uses a refresh token to obtain a new access token, Salesforce simultaneously issues a brand new refresh token and invalidates the previous one. A refresh token can only be used once. If a compromised token is used by a threat actor, the legitimate client’s token is also invalidated — Salesforce detects the double-use and invalidates the entire token family, forcing re-authentication.

This is a significant security improvement for applications that store long-lived refresh tokens. It limits the blast radius of a stolen token to a single use.

Who it was designed for:
Any application that stores and reuses refresh tokens — primarily Pattern B applications.

Impact on Pattern A (JWT Bearer):
Zero. JWT Bearer never returns a refresh token. There is nothing to rotate. Enabling RTR on an ECA used exclusively for JWT Bearer authentication has no observable effect on that authentication path.

Impact on Pattern B (Auth Code) — HIGH RISK :
Significant code changes required before enabling. Applications must:
— Capture and store the new refresh token returned in every token refresh response (the old one is now invalid)
— Handle the invalid_grant error that occurs when a rejected/compromised token is used
— Handle race conditions in applications that may attempt parallel refresh token requests with the same token

Critical warning — shared ECAs:
If your ECA is shared across multiple integrations (some using JWT Bearer, others using Auth Code with refresh tokens), enabling RTR affects all integrations on that ECA simultaneously. Any Auth Code integration that has not updated its code to handle rotation will fail silently on its next token refresh. RTR cannot be disabled once enabled. Coordinate with all integration owners before enabling in production.

“As per salesforce , there isn’t a phased rollout at this time, so please plan to enable RTR by May 11. All integrations on the shared ECA that use refresh tokens need to be updated to capture and store the new rotated token before the deadline.”

Action for JWT Bearer-only ECAs:
Enable the checkbox. No code changes needed. Low risk.

Action for shared or Auth Code ECAs:
Identify all integrations on the ECA. Confirm each owner has updated their token refresh handling. Only then enable in production.

Requirement 3 — Idle Refresh Token TTL (30 Days)

What it is:
This setting configures Salesforce to automatically invalidate refresh tokens that have not been used for 30 consecutive days. Previously, refresh tokens were often configured as “valid until revoked” — meaning a token stored months or years ago could still be used to obtain fresh access tokens indefinitely.

Impact on Pattern A (JWT Bearer):
Zero. No refresh tokens exist to expire.

Impact on Pattern B (Auth Code):
Low, with one caveat. Active applications that refresh tokens regularly will reset the 30-day idle clock on every use and will not be affected. The only
affected scenario is a token that has sat genuinely idle for over 30 days — for example, a user who authenticated once, left the application, and returns months later expecting their session to persist. Those sessions will require re-authentication.

Action:
Enable the “Limit Idle Refresh Token Time-to-Live (TTL) to 30 Days” checkbox. For JWT Bearer applications, this is a no-op. For Auth Code applications, the risk is limited to long-dormant sessions.

Requirement 4 — Refresh Token IP Range Allowlist

What it is:
This control restricts refresh token requests to originate only from predefined IP address ranges. Any refresh token request from an IP outside the configured ranges is automatically rejected, preventing stolen tokens from being used outside your known infrastructure.

“As per salesforce ,enable this feature only if your CA/ECA uses a callback URL that is not a: Localhost, Salesforce org, Custom URL scheme.”

This means the requirement is explicitly not applicable when your ECA’s callback URL points to a Salesforce org (e.g.https://yourorg.my.salesforce.com/services/oauth2/success or https://login.salesforce.com/services/oauth2/success), a localhost address, or a custom URL scheme.

“Salesforce confirmed that the IP Allowlist requirement does not apply for the callback https://login.salesforce.com/services/oauth2/success. The documentation exempts Salesforce org, localhost, and custom URL scheme callbacks.”

Impact on Pattern A (JWT Bearer):
Not applicable — regardless of callback URL, JWT Bearer never issues refresh tokens.

Impact on Pattern B (Auth Code) with Salesforce org callback:
Not applicable — the callback URL exemption covers this case.

Impact on Pattern B (Auth Code) with non-Salesforce callback:
Applicable. You must define the IP ranges from which your infrastructure makes refresh token requests (maximum 256 IP addresses across all ranges) and configure them in the ECA settings.

Action:
Evaluate your callback URL. If it points to a Salesforce org, localhost, or custom URL scheme — this requirement does not apply. Document the exemption for your compliance records.

Testing Your Changes Before Production

Regardless of which controls apply to your ECA, validate in a sandbox before enabling in production. Here is the approach I used:

For JWT Bearer applications:
Replicate your production ECA certificate in a Full Sandbox — copy the certificate from your production ECA (under JWT Bearer Flow → Certificate Upload) and upload it to the Full SB ECA. Update the sandbox username with the sandbox suffix.

Baseline test before any changes — use Postman to POST a JWT assertion to your sandbox token endpoint. Verify you receive an access token with no refresh_token in the response.

Enable all three applicable controls in Full SB (PKCE, RTR, Idle TTL) — save after each.

Re-run the same Postman request — the response should be identical to the baseline. If it is, your JWT Bearer flow is unaffected by all three controls.

Apply changes to production — enable PKCE and Idle TTL first. Validate. Enable RTR last, only after confirming any shared Auth Code integrations on the ECA are ready.

Why this matters:
RTR cannot be disabled once enabled. A premature enable that breaks other integrations sharing your ECA is not recoverable without re-authentication across all affected users. Sequence matters.

The Shared ECA Problem — A Practical Coordination Guide

Many enterprise ISV applications share an ECA across multiple internal integrations — some using JWT Bearer, others using Auth Code. This is where the May 11 deadline creates real operational risk.

The challenge: RTR affects all integrations on the ECA simultaneously. You cannot enable it selectively per flow or per integration. If integration A is ready and integration B is not, enabling RTR will break integration B on its next token refresh.

What you need from each Auth Code integration owner on the shared ECA:
Confirmation that their code captures the new refresh token returned in every token refresh response — not just on first authentication

Confirmation that they handle invalid_grant errors gracefully — forcing re-authentication rather than entering an error loop

Confirmation that parallel refresh requests are handled — if two threads attempt to refresh the same token simultaneously, only one will succeed; the other receives an invalid token error

Until you have written confirmation from every integration owner on the shared ECA, enabling RTR in production is a risk that must be explicitly accepted and owned by whoever manages the ECA.

Summary

If your ECA uses JWT Bearer only:

If your ECA uses Auth Code with refresh tokens:

Conclusion

The Salesforce May 2026 ECA security mandate created significant noise in the AppExchange partner community — much of it unnecessary. For ISVs using JWT Bearer flows, the compliance path is straightforward: enable three checkboxes, document the IP Allowlist exemption, and validate in sandbox. No client code changes are required.

For ISVs using Auth Code flows with refresh tokens, the work is more involved — particularly Refresh Token Rotation, which requires code changes and cross-team coordination before it can be safely enabled in production.

The key decision is understanding exactly which OAuth patterns your ECA uses before attempting to apply the requirements. Applying every control blindly to every integration is how teams create production incidents on a compliance deadline.

If you are an AppExchange ISV architect navigating this deadline, I hope this breakdown saves you the two weeks it took me to work through it from first principles.

Top comments (0)