Appnigma

The June 25 Salesforce Connected App deadline: what it actually required

salesforce connected app security deadline

Jun 26, 2026

12 min read

The June 25 Salesforce Connected App deadline: what it actually required

June 25 came and went. By mid-June I'd been on calls with three different founders who had spent the last six weeks migrating their 1GP Connected App to an External Client App because their lawyer, their partner manager, or some blog post had told them the deadline required it.

It didn't. None of them needed to do that migration. They burned a quarter for nothing.

This is what the deadline actually said, what Salesforce actually accepts, and the path almost nobody's writing about, which is the one most of you should pick. I'll spend most of the doc on that one.

What the rule says

Five controls. They apply if (a) the Connected App or ECA was created in your ISV infrastructure orgs (Partner Business Orgs, packaging orgs, namespace orgs, dev orgs where you mint the credentials your customers consume) AND (b) it's in use by more than two customer production orgs. If both, the controls apply. If not, it doesn't.

Customer-created Connected Apps in customer orgs? Out of scope. Connected App used in one or two prod orgs? Out of scope (rare for anyone real).

The five controls, with the only commentary that matters per control:

PKCE on authorization code flows. Generate code_verifier and code_challenge per flow, send code_challenge_method=S256 (Salesforce only supports S256, plain doesn't work, this catches people), exchange the verifier on token exchange. Doesn't apply to JWT Bearer, doesn't apply to SAML Assertion, doesn't apply to the deprecated Username-Password flow. If you're not running an authorization code flow, PKCE isn't a thing you implement.

Refresh Token Rotation. Every refresh swaps the old token for a new one. The old one becomes a trap. We'll come back to why.

30-day idle timeout. Refresh tokens that go unused for 30 days expire. If your integration can sit idle longer than 25 days, you need a heartbeat. Mobile apps are uniquely bad at this.

Refresh token IP allowlist. Refresh tokens can only be redeemed from IPs your ISV pre-registers with Salesforce. If your backend is on Heroku or Lambda, you don't have a stable outbound IP. This is the longest-lead item. Start it today.

Refresh token IP monitoring. Recommended, not strictly enforced. Log every refresh, alert on weirdness.

PKCE is cheap, RTR is dangerous if you get it wrong, the 30-day clock is annoying for mobile, the IP allowlist is operationally the hardest. We'll go in that order.

Connected App vs External Client App, briefly

Two different metadata types in Salesforce. Connected App is the older one, ECA is the newer one. The deadline doesn't require you to switch.

Connected Apps: single metadata file, open-by-default access, designed for 1GP, work in 2GP with workarounds. ECAs: split global/local config files, secure-by-default, native 2GP, no Username-Password OAuth support, no Push Notifications. As of Spring '26 you can't create new Connected Apps in fresh orgs. Existing ones keep working.

A lot of the "you must migrate to ECA" reading of the deadline confuses the direction of travel (ECAs are clearly Salesforce's future) with the deadline itself (which is about five controls, not about packaging types). They're not the same thing.

The three paths

Path A: leave your Connected App where it is, toggle the controls

Open your existing packaged Connected App in your packaging org. Enable PKCE and Refresh Token Rotation in settings. Salesforce auto-propagates to every subscriber. No new Consumer Key. No customer re-auth.

You still have to:

  • update your app code for PKCE

  • persist the rotated refresh tokens transactionally

  • stand up static egress and register the IPs

  • add the heartbeat if any customer can idle past 25 days

You don't have to: migrate to ECA, rebuild your packaging story, resubmit Security Review, force customers to do anything.

Right answer if your 1GP works and you weren't already planning to move.

Path B: full migration to ECA in a new 2GP package

The thorough path. New ECA, new 2GP managed package, new Consumer Key, customer re-auth across your installed base, fresh Security Review submission.

Three to six months of work depending on integration complexity, plus the customer coordination. Right answer if you were already doing a 1GP-to-2GP migration for other reasons, or your Connected App is still using Username-Password OAuth (which ECAs don't support but which is going away anyway).

Wrong answer if you're doing it just because of the deadline. The deadline did not ask for this.

Path C: the sidecar 2GP

This is the path I've been recommending. Hardly anyone writes about it.

You create a separate 2GP managed package that contains only the External Client App metadata. Nothing else. You distribute it alongside your existing 1GP package. Customers install both (it's a one-click extra install on top of what they already have).

Your app code starts using the new Consumer Key from the ECA for the OAuth flows that need to be compliant. Your existing 1GP package keeps running unchanged. The sidecar gives you the ECA security model without the rest of the migration.

Why this works:

  • the sidecar package is tiny (just the ECA), so the build is fast (1-2 weeks)

  • Security Review on a tiny package is faster than on a full one (about 3 weeks queue from what I've seen in 2026)

  • customer install friction is low: a single-click install of a small companion package

  • your 1GP keeps working forever, no forced migration timeline

  • if you ever decide to do the full 1GP-to-2GP migration, the ECA's already in place

It's a real, supported pattern. Salesforce documents it in the ISVforce Guide if you read carefully. The reason it's underused is that the headlines say "you must move to ECA" and most people read that as "you must move everything to ECA in 2GP."

Three founders I've talked to since the deadline have taken this path. The most recent: a revenue intelligence ISV with 80 enterprise customers. Their initial scoping for full Path B was 12 weeks of eng plus 3 months of customer coordination. Path C took them about 9 weeks end to end (most of that was the Security Review queue, not the build). Same compliance outcome, about a third of the cost.

How to decide

Modern OAuth flows (Web Server, Auth Code), no other reason to move to 2GP: Path A.

Already planning a 2GP migration, or using Username-Password OAuth: Path B.

Want ECA without the migration cost: Path C.

That's the whole decision. Don't overthink it.

PKCE: the cheap one

PKCE is small enough that I usually tell teams to ship it first as a confidence-builder. Generate a fresh verifier/challenge pair per flow, persist the verifier server-side keyed by state, send the challenge in the authorize call and the verifier in the token exchange. Always S256.

const crypto = require('crypto');

function generatePKCEPair() {

const codeVerifier = crypto.randomBytes(64).toString('base64url');

const codeChallenge = crypto

.createHash('sha256')

.update(codeVerifier)

.digest('base64url');

return { codeVerifier, codeChallenge };

}

function buildAuthorizeURL({ instanceUrl, clientId, redirectUri, codeChallenge, state }) {

const params = new URLSearchParams({

response_type: 'code',

client_id: clientId,

redirect_uri: redirectUri,

code_challenge: codeChallenge,

code_challenge_method: 'S256',

state

});

return `${instanceUrl}/services/oauth2/authorize?${params.toString()}`;

}

async function exchangeCodeForTokens({

instanceUrl, clientId, clientSecret, redirectUri, code, codeVerifier

}) {

const r = await fetch(`${instanceUrl}/services/oauth2/token`, {

method: 'POST',

headers: { 'Content-Type': 'application/x-www-form-urlencoded' },

body: new URLSearchParams({

grant_type: 'authorization_code',

client_id: clientId,

client_secret: clientSecret,

redirect_uri: redirectUri,

code,

code_verifier: codeVerifier

})

});

if (!r.ok) throw new Error(`Token exchange failed: ${r.status}`);

return r.json();

}

The state parameter is your CSRF protection. People skip it. Don't.

RTR: the one that bites

Here's the failure mode that almost nobody explains until they hit it.

When you refresh a token, Salesforce returns a new refresh token and marks the old one invalid. So far, fine. But if any code on your side later tries to use the old token, Salesforce treats that as evidence of a stolen-token incident. The penalty isn't just "this attempt fails." Salesforce invalidates every active refresh token for that user-app pair.

The realistic case where this happens: two workers in your backend both try to refresh the same token at the same time. One wins the exchange and gets the new token. The other one is still holding the old token. When the old-token worker eventually tries to use what it has, Salesforce nukes everything. The customer has to re-authenticate. You find out from a support ticket.

The fix is token-locking. Before any worker refreshes a token, it acquires a lock on portalId + clientId. Only one worker runs the exchange. The others wait, then re-read the now-updated token from the database.

async function refreshWithLock(redisClient, db, portalId, clientId, clientSecret) {

const lockKey = `sf:refresh-lock:${portalId}`;

const lock = await redisClient.set(lockKey, '1', 'NX', 'EX', 30);

if (!lock) {

// Someone else is refreshing. Wait briefly, re-read.

await new Promise(r => setTimeout(r, 500));

return await db.getTokens(portalId);

}

try {

const current = await db.getTokens(portalId);

const fresh = await callSalesforceRefresh(current.refresh_token, clientId, clientSecret);

await db.saveTokens(portalId, fresh);

return fresh;

} finally {

await redisClient.del(lockKey);

}

}

In a high-volume backend you'd want proper queue-based waiting instead of setTimeout, but the principle's the same: serialize refresh per token chain.

If you remember nothing else from this doc, remember the cascade-invalidation behavior. It's the single thing that turns a smooth RTR migration into a customer-facing incident.

The 30-day clock

A daily worker that refreshes any token whose last refresh is older than 25 days. Five-line cron job, three-line database query, idempotent.

async function refreshHeartbeat(db) {

const stale = await db.getTokensWithRefreshOlderThan(25);

for (const t of stale) {

try {

await refreshWithLock(redis, db, t.portalId, clientId, clientSecret);

await db.markRefreshed(t.portalId, new Date());

} catch (err) {

// Customer may have revoked. Flag for re-auth.

await db.markNeedsReauth(t.portalId, err.message);

}

}

}

Mobile is the awkward case. The refresh token lives on the device. If the customer doesn't open the app for 30 days, the token expires. Push notification reminders help; graceful re-auth on next open helps. There isn't a clean engineering fix in 2026. It's a UX tax on inactive users.

Static IPs: just start

If you're on shared egress today (Heroku, AWS Lambda, anything without a fixed outbound IP), this is the part of compliance with the longest lead time and the least flexibility.

Three options I see most often:

  • AWS NAT Gateway with Elastic IPs, route outbound through it. ~$50-200/month per region depending on traffic. 1-2 days to set up.

  • A third-party static-egress service like QuotaGuard or Fixie. Fast to set up, per-request pricing stacks at scale.

  • Roll your own proxy with fixed IPs. More work, more control.

Once you have the IPs, file a support case with Salesforce to register them. Turnaround in my experience: 5-10 business days.

If you haven't started: today.

What Salesforce actually does to non-compliant apps

The stated penalty is AppExchange de-listing and "temporary or permanent suspension of your application's interoperation with Salesforce." That's the worst case.

What I've actually watched happen through May and June: Salesforce sent written posture-update requests rather than immediately de-listing anyone. Comply quickly, document your gaps, send a written plan to your partner account team, and you're treated as in good standing while you finish. The apps at real de-listing risk are the ones that ignore the deadline silently.

The relationship part matters more than the calendar part. Stay in touch with your partner manager.

A recent example

Revenue intelligence ISV, 80 enterprise customers, 1GP Managed Package shipping a Connected App. Their OAuth flows: all Web Server (Auth Code) and JWT Bearer. Existing 1GP working fine. No other reason to migrate to 2GP this year.

Initial read: full ECA migration. Initial scope: 12 weeks of eng plus 3 months of customer coordination.

We talked through it. PKCE applies to their Web Server flows. RTR applies to those too. JWT Bearer doesn't get refresh tokens so RTR is structurally inapplicable there. Path C made sense.

Week 1-2: tiny 2GP project with just the ECA, replicating the existing Connected App's OAuth config. Week 3: app code change to use the new Consumer Key for the relevant flows. Week 4: Security Review submission for the sidecar. Weeks 5-8: customer rollout (single-click install of the companion package), 90% done in two weeks. Weeks 9-12: tail of customers, operational hardening (heartbeat, static IPs, monitoring).

About $45K in eng time. No customer churn. Compare to their initial Path B scoping at roughly $120K and a quarter of distraction. Same compliance outcome.

Path C isn't always right. But when it is, the cost difference is significant.

Three questions I get

Do I actually have to do anything if my flows are all JWT Bearer?

PKCE and RTR don't apply (no auth code flow, no refresh tokens). The static IP allowlist may still apply depending on your broader app configuration. The 30-day idle timeout doesn't apply since there are no refresh tokens to age out. Audit per flow. Pure JWT Bearer setups often need less than people fear.

What about customers who created their own Connected App in their own org?

Out of scope for the June 25 mandate. The mandate is for ISV-created credentials. That said, the next AppExchange Security Review cycle is going to push customers in the same direction, so it's worth getting ahead of it.

Can I really keep a 1GP Managed Package forever and just bolt on a sidecar 2GP?

Yes. Salesforce supports it. Your 1GP keeps working unchanged. The sidecar needs to go through Security Review separately but it's a tiny submission. You can ship the full 1GP-to-2GP migration on your own timeline, or never. I've stopped recommending the full migration to anyone who isn't already doing it for other reasons.

Sources

  1. Salesforce Partner Community, Mandatory Security Updates for Connected Apps and ECAs (May 2026): https://partners.salesforce.com/pdx/s/pcnews/mandatory-security-updates-for-connected-apps-and-ecas-MCFBLDLDQ2TVDZFA22GLAMBVEZGY

  2. Salesforce ISVforce Guide, Secure Your Connected Apps and External Client Apps: https://developer.salesforce.com/docs/atlas.en-us.packagingGuide.meta/packagingGuide/secure_code_ac_eca.htm

  3. Salesforce Help, Proof Key for Code Exchange (PKCE) Extension: https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_pkce.htm

  4. Salesforce Help, Comparison of Connected Apps and External Client Apps Features: https://help.salesforce.com/s/articleView?id=xcloud.connected_apps_and_external_client_apps_features.htm

  5. Salesforce Help, Rotate Refresh Tokens: https://help.salesforce.com/s/articleView?id=release-notes.rn_security_refresh_token_rotation.htm

  6. Beyond The Cloud, Salesforce Connected App to ECA: What the May 11, 2026 Deadline Actually Requires: https://blog.beyondthecloud.dev/blog/salesforce-connected-app-to-eca-what-the-may-11-2026-deadline-actually-requires-and-what-it-doesnt

Ready to transform your Salesforce experience?

Start exploring the Salesforce Exchange today and discover apps that can take your CRM efficiency to the next level.

decorative section tag

Blog and News

Our Recent Updates