An External Client App (ECA) splits its OAuth setup between two people. You configure the OAuth settings once in your own org, such as the callback URL, scopes, enabled flows and PKCE. Each customer's Salesforce admin then sets the OAuth policies in their org, such as who may connect and how long refresh tokens live. Salesforce's ECA overview says those policies "are never packaged, and only affect the admin's local org."
That split matters most to a SaaS company whose product connects to many customer orgs. If you're a founder or CTO building the native Salesforce integration enterprise customers asked for, the ECA is now the starting point, because creating connected apps is restricted as of Spring '26. Settings you can fix in your next release. Policies sit with each customer's admin, so a wrong default there turns into a support ticket in every org that installed you. Once more than two customer production orgs use the app, Salesforce's partner rules also require four OAuth security controls.
Appnigma's integration infrastructure is built for this part of the work. It lets engineers "handle app registration and authorization with infra built for the systems your customers use," and lets "each customer authorize their own account from your product, with their connection managed separately." If you need the basics first, our guide to what an External Client App is and how to create one covers them. This page covers the OAuth configuration.
Updated October 2026. Checked against Salesforce Help, the Metadata API Developer Guide and the ISVforce Guide. The earlier version of this page compared OAuth 1.0 with OAuth 2.0 and never covered External Client Apps. It also gave an OAuth 1.0 removal date and token lifetimes that Salesforce doesn't publish. This version replaces it with the ECA OAuth settings, policies, flows and partner security controls.
What you configure and what each customer's admin controls
Salesforce stores an ECA's OAuth configuration in separate metadata files, and who owns each file decides where a fix has to happen. The global OAuth settings hold "private and sensitive OAuth consumer information that can't be packaged and must not be added to source control." The OAuth policies are "configured by the admin" in each org.
| Setting | Who sets it | Shipped in your package? | What it controls |
|---|---|---|---|
| Callback URL, consumer key and secret | You | Never | Where Salesforce sends the authorization code, and how your app identifies itself |
| PKCE, refresh token rotation, enabled flows | You | Never | Which flows work and how strict the token exchange is |
| JWT certificate | You | Never | Turns on the JWT bearer flow |
| OAuth scopes, trusted IP ranges | You | Yes | What a token can reach, and up to 128 IP ranges |
| Permitted users | Customer admin | Default only | Whether users self-authorize or only pre-authorized profiles can connect |
| Client credentials run-as user | Customer admin | No | Which user the client credentials flow acts as |
| Refresh token policy, session timeout | Customer admin | Default only | How long tokens stay valid in that org |
| IP relaxation, start URL | Customer admin | Default only | Whether IP restrictions apply, and where users land after login |
Two rules from Salesforce Help shape how you ship this. The first is that admin choices win over your updates. "After an admin updates policies, the values persist even if the developer updates the app Settings," says the OAuth policies page. Package defaults only help customers who haven't changed anything yet. You set them on the Package Defaults tab.
The second is that your source org becomes a production dependency. "If you delete the external client app from the source org, the subscriber org's external client app breaks," according to Package an ECA. Salesforce's 2GP component list tells ISVs to define the ECA in their Partner Business Org Dev Hub, because ECAs can't be created from Setup in a scratch org. Treat that org like production. Salesforce also documents 1GP packaging, but only "in the org where they were created," and recommends 2GP.
Which OAuth flow fits each kind of SaaS integration
Most SaaS integrations need one of three flows, and the choice decides what each customer's admin has to do during onboarding. Salesforce's OAuth flows page also recommends "avoiding the user-agent and username-password flows because they transmit credentials."
| What the integration does | Flow | What the customer's admin does | Refresh token |
|---|---|---|---|
| Users in each customer org connect your product | Web server flow with PKCE | Approves the app and sets permitted users | Yes, with a 30-day idle limit |
| A background sync runs as one integration user | Client credentials | Picks a run-as user with the API Only permission | None |
| A server signs its own requests with a certificate | JWT bearer | Pre-authorizes profiles or permission sets | None |
The web server flow fits most customer-facing products, because every user approves access and you get a refresh token per org.
Client credentials is simpler to run but leans harder on the customer. The run-as user is a policy, so each admin picks it in their own org, and the Metadata API reference says "this user must have the API Only permission." The client credentials page adds two constraints. The flow "doesn't support refresh tokens," and token requests to login.salesforce.com or test.salesforce.com aren't supported, so you call each customer's My Domain URL. The same page warns that "any person or app that has access to your external client app's consumer key and consumer secret can get an access token." A leaked secret therefore reaches every org whose admin enabled the flow.
JWT bearer needs an X.509 certificate in your global settings and prior approval in each org. The JWT bearer page says the flow "never issues a refresh token" and that the certificate "can't exceed 4 KB."
Two flows are off the table for a packaged app. The comparison article says "the OAuth username-password flow won't be made available for external client apps." The device flow is being restricted to local ECAs with a localhost callback URL, and "starting in November 2026: Salesforce blocks the device flow for all non-compliant apps," which includes packaged ECAs.
What a web server flow with PKCE sends
PKCE (Proof Key for Code Exchange) binds the authorization code to a random string your server generates, so an intercepted code is useless on its own. ECAs require it by default. The isPkceRequired field is "true (default)" in the global settings reference. Your server creates a code verifier of 43 to 128 characters and sends its SHA-256 hash, Base64URL-encoded, as the code challenge.
GET https://login.salesforce.com/services/oauth2/authorize
?response_type=code
&client_id=YOUR_CONSUMER_KEY
&redirect_uri=https://app.example.com/oauth/salesforce/callback
&code_challenge=BASE64URL_SHA256_OF_VERIFIER
&code_challenge_method=S256
&state=RANDOM_CSRF_VALUEAfter the user approves, your callback receives a code and exchanges it.
POST https://login.salesforce.com/services/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTHORIZATION_CODE
&client_id=YOUR_CONSUMER_KEY
&client_secret=YOUR_CONSUMER_SECRET
&redirect_uri=https://app.example.com/oauth/salesforce/callback
&code_verifier=ORIGINAL_VERIFIERA server-side app counts as a confidential client. The ISVforce Guide therefore requires both values in the token request. The code_verifier is there "for code binding" and the client_secret is there "for client authentication." Store the returned refresh_token and instance_url per customer org, and send API calls to that instance_url. Customers on sandboxes start at test.salesforce.com instead, per the OAuth endpoints page. These requests follow the documented parameters. Test them against your own org before you ship.
The four OAuth controls partner apps had to turn on by May 11, 2026
The ISVforce Guide page Secure Your Connected Apps and External Client Apps applies to any partner ECA "in use by more than two customer production orgs." All four controls were due on May 11, 2026. Failing to comply "may result in the Partner Application's AgentExchange de-listing" or suspension of its access to Salesforce. After you self-attest, "the security controls are locked and can't be disabled."
| Control | Where you turn it on | Client change it forces |
|---|---|---|
| PKCE | OAuth settings | Send code_challenge and code_verifier on every authorization |
| Refresh token rotation | OAuth settings | Save the new refresh token after every refresh |
| 30-day idle refresh token limit | OAuth settings | Refresh before a token sits unused for 30 days |
| Refresh token IP allowlist | OAuth settings | Refresh tokens only from your own server IP ranges |
Rotation is the one that breaks working code. The old refresh token dies on each refresh, and the refresh token flow page says that if a rotated-out token is used again, "Salesforce invalidates the current refresh token and any associated access tokens." Two workers refreshing the same org at once can lock that customer out. Salesforce's advice is to "avoid sending simultaneous requests that contain the same refresh token," and Login History shows "Token request is already being processed" when it happens. In practice that means one refresh at a time per org, behind a lock.
The idle limit affects customers who use you rarely. Here is the arithmetic from the documented rule. A connection that syncs weekly resets its 30-day window every 7 days and never expires. A customer who pauses the integration for a quarter has a dead token by day 31 and must re-authorize. A scheduled refresh for each org every 25 days or so keeps quiet connections alive, because the window "resets" each time the token is used, per the idle TTL page.
The IP allowlist needs fixed egress addresses, so serverless jobs with changing IPs can't refresh tokens. The two official sources state the limit differently. The ISVforce Guide says "a total of 256 IP addresses are allowed across all IP ranges." The Help setup page says up to 128 ranges "with no more than 10,000 IP addresses total." Plan for the smaller number.
How long access and refresh tokens last in a customer's org
An access token doesn't have a fixed lifetime of its own. It follows the session timeout. The session policies page lists where that value comes from "in that order." The app setting wins first. The user's profile comes next, and the org's Session Settings come last. Only the org-level default is two hours, as the login() reference states. A customer whose profile sets 30 minutes gives you 30-minute tokens, so refresh on a 401 response instead of on a timer.
Refresh tokens follow the customer's refresh token policy. The policies reference lists four types. They are Infinite, SpecificInactivity, SpecificLifetime and Zero (expire immediately). For ECAs, Salesforce now enforces the 30-day idle limit, and the idle TTL page says the "valid until revoked" policy "is no longer valid with the idle TTL limit enforced." It also notes that "Salesforce doesn't automatically update the app's metadata," so set the policy type to SpecificInactivity with 30 days in your source files to match.
Flows without refresh tokens are capped too. With the idle control on, it "caps the access token lifetime at 30 days from the last login date," so client credentials and JWT bearer integrations request a new token rather than holding one.
Does Salesforce still support OAuth 1.0?
For new integrations, the answer that matters is no. Every flow you can configure on an External Client App is an OAuth 2.0 flow. Salesforce still documents a legacy OAuth 1.0.A flow, and the SOAP API guide says "Salesforce supports OAuth 1.0.A and 2.0 for SOAP API requests," so claims that it was removed years ago aren't supported by the docs. If you're evaluating old integration code, the practical question is whether it runs through a connected app you still control, because new connected apps can't be created and the migration article makes moving a packaged one to an ECA "the ISV's (the vendor's) responsibility."
Errors customers see after they connect
Salesforce's OAuth error table still says "connected app," but the same codes come back for ECAs. Each one below points to the setting or policy that caused it.
- invalid_grant, "expired access/refresh token." The token sat idle past 30 days or hit the customer's refresh token policy. The user re-authorizes, and a scheduled refresh prevents it next time.
- invalid_grant right after a refresh. Two processes used the same refresh token under rotation, and Salesforce revoked both. Serialize refreshes per org.
- invalid_grant on a client credentials request. The request went to login.salesforce.com instead of the customer's My Domain URL, or no run-as user is set in the policy.
- invalid_grant, "IP restricted or invalid login hours." The request came from outside your refresh token allowlist, or the customer's IP relaxation policy enforces their own ranges.
- invalid_app_access. The policy is set to admin-approved users, and this user's profile or permission set isn't on the list.
- redirect_uri_mismatch. The callback in the request doesn't match one in your settings. Multiple callback URLs are allowed, up to 2,000 characters combined, per the OAuth settings page.
Fixes for the rest of the codes are in our Salesforce OAuth errors reference.
Frequently Asked Questions
Where do I find the consumer key and secret for an External Client App?
In External Client App Manager, open the app's Settings tab, expand OAuth Settings and click Consumer Key and Secret. Salesforce may ask you to verify your identity first, per the Salesforce CLI ECA guide. ECAs live in External Client App Manager, not App Manager, and "Manage Consumer Details" is the connected app label.
What should the OAuth start URL be in an External Client App?
It should be the page users need to land on after they authenticate, often your app's own page. Each customer's admin sets it on the Policies tab, per Manage the Start URL. It isn't the callback URL, which your app receives the authorization code on.
Can we keep using our existing connected app?
Yes. Existing connected apps keep working after Spring '26, but new ones can't be created, and Salesforce says End of Support for existing connected apps will be announced "soon." Our connected app vs External Client App comparison covers the move.
Can Appnigma set up OAuth for our External Client App?
Yes. Appnigma's Salesforce External Client App setup hands you "your client ID (Salesforce's consumer key), its secret and a managed package link." For a full integration, a done-for-you build adds implementation and ongoing maintenance from a named integration engineer.
About the author
Sunny Chauhan is the co-founder and CEO of Appnigma AI, which builds and maintains customer integrations and native Salesforce and HubSpot apps for B2B SaaS teams. Before Appnigma, he was on Salesforce's AppExchange team, where he worked with more than 300 SaaS partners. He has spent close to a decade on the Salesforce platform, including work at Zennify, and holds Salesforce certifications including Platform Developer II, Platform App Builder, Administrator, Data Cloud Consultant, and AI Associate.
Related Articles
- What an External Client App is and how to create one
- How a SaaS product connects securely to many Salesforce orgs
- Salesforce OAuth errors, a troubleshooting reference
- Named Credentials and External Credentials in Salesforce
Sources
- External Client Apps, Configure OAuth Policies and Configure the ECA OAuth Settings, Salesforce Help
- ExtlClntAppGlobalOauthSettings and ExtlClntAppOauthConfigurablePolicies, Metadata API Developer Guide
- Package an ECA, Package an ECA with 1GP and Configure Package Defaults, Salesforce Help
- Components Available in Second-Generation Managed Packages, 2GP Developer Guide
- OAuth Authorization Flows, Client Credentials Flow, JWT Bearer Flow and Device Flow, Salesforce Help
- Comparison of Connected Apps and External Client Apps Features and New connected apps can no longer be created in Spring '26, Salesforce Help
- Secure Your Connected Apps and External Client Apps, ISVforce Guide
- Refresh Token Flow, Limit the Idle Refresh Token TTL in ECAs and Set IP Allowlist Ranges for Refresh Tokens, Salesforce Help
- Manage Session Policies for a Connected App, Salesforce Help, and login(), SOAP API Developer Guide
- OAuth Endpoints and OAuth 2.0 Authorization Errors, Salesforce Help
- Manage the Start URL for ECAs and OAuth 1.0.A Flow, Salesforce Help
- Create an External Client App in Your Org, Salesforce DX Developer Guide
- Implementing Salesforce Integrations, SOAP API Developer Guide



