---
name: salesforce-oauth-app
description: Creates a Salesforce Developer Edition org and registers an External Client App (the successor to Connected Apps) to obtain OAuth2 consumer key and consumer secret — with callback URLs, scopes, the mandatory PKCE / refresh-token-rotation / TTL / IP-binding controls, the uninstalled-app restriction, and a safe credential handoff. Use when asked to get Salesforce OAuth credentials, create a Salesforce connected app or external client app, set up a Salesforce developer org, rotate a Salesforce consumer secret, or fix a Salesforce OAuth error like `redirect_uri_mismatch`, `invalid_client_id`, or a customer who suddenly cannot authorize. For any other vendor's developer portal, use that vendor's skill instead.
---

# Salesforce OAuth2 App Registration

Get a working Salesforce OAuth2 client — a developer org, an External Client App, callback URLs, scopes, consumer key
and secret — for a platform that connects customer Salesforce orgs on behalf of many customers.

Salesforce is the most changed of the major OAuth providers right now, and the changes are not cosmetic. **Connected
Apps can no longer be created**, and four OAuth controls that used to be optional are now enforced and cannot be
turned off. Two of them — PKCE and refresh token rotation — are *client* requirements: registering the app correctly
is not enough if the code exchanging the tokens does not implement them.

The other thing to understand before you start: a Salesforce OAuth app lives in **one** org but is authorized by users
in **every customer org**. Customer orgs see it as an *uninstalled* app, and Salesforce now restricts those. §7 is the
part that decides whether customers can actually connect.

## Inputs to collect before you start

Ask in one batch. Never invent.

| Input | Notes |
| --- | --- |
| **App name**, contact email, logo | Shown on the authorization screen |
| **Which org owns the app** | An existing org, or a new Developer Edition org (§2) |
| **Callback URLs** | Every callback host your platform serves (§4) |
| **Scope set** | What the connector actually calls (§5) |
| **Does the client do PKCE and store rotated refresh tokens?** | Blocking — see §6 and §7 |
| **Static egress IPs** | For IP binding, if enforced for this app (§7) |
| **Customer install story** | Package/install vs uninstalled authorization (§7) |

## Quick Start

1. Confirm a **new app** is needed — existing connections are bound to the current consumer key (§1).
2. Sign in to, or create, the org that will own the app (§2).
3. Create an **External Client App** in Setup (§3).
4. Add **every** callback URL (§4).
5. Select the scopes the connector calls, and no more (§5).
6. Capture consumer key and secret via identity verification; note the `instance_url` rule (§6).
7. Work through the enforced controls and the uninstalled-app restriction — this is where connectors break (§7).
8. Verify by authorizing from a **second** org, not the one that owns the app (§8).
9. Hand the credentials over — never commit them (§9).

## Platform state (verified 2026-09-20 — re-verify before trusting)

Three changes land directly on this task. Check the linked articles before following any older runbook:

- **New Connected Apps can no longer be created, as of Spring '26.** The block covers the UI *and* the Metadata API.
  Existing connected apps keep working and can still be installed into new orgs via package installation. Salesforce
  Support can grant a temporary exception, but says future releases will remove even that. **Use an External Client
  App (ECA)** for anything new.
- **Four OAuth controls are now technically enforced and cannot be disabled once on**: OAuth **PKCE**, **refresh token
  rotation**, **absolute TTL** (refresh tokens expire after 30 days), and **IP binding** (requests must come from
  allowlisted static, publicly routable IPs). Partners and ISVs are told to update client code to match. Confirm the
  exact scope and enforcement date for the org and app type you are working with — this is the claim most worth
  re-reading at the source.
- **Uninstalled connected apps are restricted** since early September 2025. New users are blocked from authorizing an
  app that is not installed in their org unless they hold the **Approve Uninstalled Connected Apps** permission
  (granted to System Administrators automatically). Device-flow usage of uninstalled apps is blocked outright.

If Setup does not look like this, stop and report what you actually see rather than clicking on.

## 1. Decide: reuse the existing app, or create a new one

A new app means a **new consumer key, and every existing customer connection is bound to the old one** — every
customer would have to re-authorize. Reuse the existing app for: adding a callback URL, adding a scope, rotating a
compromised secret, or diagnosing an authorization failure.

Create a **new** app only when the user explicitly wants one: a replacement for a compromised app, a separate app for
a different product, or a deliberate migration from a legacy Connected App to an ECA. Note that migrating is itself a
customer-visible re-authorization event — it is a project, not a config change. Say which path you are taking first.

## 2. The org that owns the app

- **Developer Edition org** — free, does not expire, full API access: sign up at `https://developer.salesforce.com/signup`.
  This is the right home for an integration's OAuth app when you do not already have one.
- **An existing production org** — fine, and normal for an established platform. You need System Administrator access.
- **Sandbox** — authorization host is `test.salesforce.com` rather than `login.salesforce.com`. Useful for testing the
  enforced controls (§7) before touching production.
- **My Domain** — most orgs authorize against their own `https://<domain>.my.salesforce.com` host. Your client must
  handle all three host forms, not just `login.salesforce.com`.

Hand control back to the user for anything only they can do: signup email verification, MFA enrollment (Salesforce
requires MFA), accepting terms, or granting themselves the permissions below. Do not retry a blocked step in a loop.

**If this session has no browser automation** (the usual case for a CLI or cloud run), do not pretend to click. Hand
the user an exact, ordered click path with the literal values to paste (§4 callback URLs, §5 scope list), then
continue once they report back with the consumer key.

## 3. Create the External Client App

In the owning org: **Setup → App Manager → External Client App Manager → New External Client App**.

1. **Basic information** — app name, API name, contact email, logo and description. These appear on the consent screen
   customers see; fill them from what the user supplied.
2. **API (Enable OAuth Settings)** — turn OAuth on. This reveals callback URLs, scopes and the flow toggles.
3. **Distribution State** — *Local* (this org only, authorized by other orgs as an uninstalled app) or *Packaged*
   (distributable to customer orgs via a managed package). For a multi-tenant connector this choice interacts with
   §7's uninstalled-app restriction — read that section before deciding.
4. **Flow enablement** — enable the **Authorization Code and Credentials Flow** (web server flow) for a customer-
   authorizes-us connector. Leave client-credentials, device and JWT flows off unless the user asked for them.
5. **Policies** — after creation, the app's **Policies** tab holds permitted users, refresh token policy, session
   policy and the security controls. "Admin approved users are pre-authorized" gives admins the most control;
   "All users may self-authorize" is what a self-serve connector usually needs. Changing this changes who can connect.

ECAs split configuration into **global** OAuth settings (consumer key and secret) and **local** settings (everything
else), which is how they avoid shipping credentials inside packages. If a field you expect is missing, check whether
it belongs to the other half.

## 4. Callback URLs

Register **every** callback host your platform serves — Salesforce accepts multiple, one per line, and matches them
exactly. For Unified.to these are one per data center; confirm the current list with the platform owner rather than
assuming:

```
https://api.unified.to/oauth/code          # us (default)
https://api-eu.unified.to/oauth/code       # eu
https://api-au.unified.to/oauth/code       # au
https://api-dev.unified.to/oauth/code      # dev
```

Notes:

- The `redirect_uri` you send must match a registered value **exactly** — no subdirectory matching, no trailing-slash
  forgiveness. Mismatch returns `redirect_uri_mismatch`, at connect time rather than at save time.
- The callback URL is your platform's, not Salesforce's. It does not change per customer org, even though the
  *authorization host* does (`login` / `test` / My Domain).
- Changes to callback URLs can take a few minutes to propagate.

## 5. Scopes

Salesforce scopes are space-delimited in the authorize URL. The ones that matter for an API connector:

| Scope | Why |
| --- | --- |
| `api` | Call the REST/SOAP APIs as the authorizing user |
| `refresh_token` | Receive a refresh token at all |
| `offline_access` | Keep that refresh token usable when the user is not present |
| `openid` `profile` `email` | Identity only — for sign-in flows, not data access |

**Unified.to's Salesforce connector uses `api`, `refresh_token` and `offline_access`** for every data object, plus
`openid profile email` for the identity/login flow. Confirm the current set with the connector's owner before
submitting.

Avoid `full`. It grants far more than any connector needs, it is a standard flag in AppExchange security review, and
customer security teams reject it. Request the narrow set and say in your summary what you chose and why.

Two ways scope requests fail that look like Salesforce being broken:

- The org lacks **API Enabled** (Professional Edition without the API add-on). No scope fixes that; the customer needs
  the add-on or a different edition.
- The authorizing **user's profile** lacks API access or the permission to use the app. Scopes cap what a token can
  do; they never exceed what the user may do.

## 6. Capture the credentials

From the **External Client App Manager** → your app → **Edit Settings** (or **Settings**) → expand **OAuth Settings**
→ **Consumer Key and Secret** → verify your identity. Salesforce emails or prompts for a verification code before it
shows the values; there is no way to read them without that step.

Capture:

- **Consumer key** (the OAuth `client_id`) and **consumer secret** (`client_secret`)
- Authorization host(s) in play: `https://login.salesforce.com`, `https://test.salesforce.com` (sandbox), or the
  customer's My Domain
- Endpoints: `/services/oauth2/authorize` and `/services/oauth2/token` on that host
- Whether this is a production or sandbox app

**The `instance_url` rule.** The token response returns an `instance_url`, and *that* is the API base for every
subsequent call — not `login.salesforce.com`, and not a host you can guess from the org name. A client that hardcodes
the login host authenticates fine and then 404s on every API request. Store `instance_url` alongside the token.

Rotating the consumer secret invalidates the old one immediately: existing access tokens keep working until they
expire, but every token exchange and refresh fails until the new secret is deployed. Never rotate without explicit
go-ahead and a cutover plan.

Report the secret once so the user can paste it into their secret store, say plainly that it is now in the transcript
and can be rotated, then move on.

## 7. The enforced controls and the uninstalled-app restriction

This section is why Salesforce connectors break. None of it is fixed by re-registering the app.

**The four enforced controls** — PKCE, refresh token rotation, 30-day absolute TTL, IP binding — cannot be disabled
once enforced, and three of them are client-side work:

| Control | What the client must do |
| --- | --- |
| **PKCE** | Generate a `code_verifier`, send `code_challenge` (S256) on authorize, send `code_verifier` on exchange. A client that never sent PKCE will start failing at the exchange. |
| **Refresh token rotation** | Store the **new** refresh token returned by each refresh. A client that keeps reusing the original will fail on the second refresh. |
| **Absolute TTL** | Refresh at least once every 30 days, or the connection dies and the customer must re-authorize. Idle connections are the ones that break. |
| **IP binding** | Call from static, publicly routable, allowlisted IPs. Serverless or autoscaled egress without stable IPs is a problem to solve before launch, not after. |

**Before registering anything, ask whether the client already does PKCE and stores rotated refresh tokens.** If the
answer is no or unknown, say so plainly — the app will register fine and then fail in production. That is a
finding to report, not a step to work around.

Two things to establish separately, because a platform can support PKCE without using it:

1. **Is the PKCE code path implemented?** (Does the client know how to send `code_challenge` and `code_verifier`?)
2. **Is it switched on for Salesforce?** A connector that supports PKCE per-connection but ships with it off will
   behave exactly like one that cannot do PKCE at all.

As of 2026-09-20, **Unified.to's Salesforce connector is in that state**: PKCE is implemented and Salesforce is
marked as optionally supporting it, but it is **off by default** and must be opted into per workspace connection —
including for connections using Unified.to's shared credentials. Under an org or app that enforces PKCE, every new
authorization needs that opt-in, or the flag flipped connector-wide. Confirm the current state with the connector's
owner before you register an ECA, rather than assuming either answer.

**The uninstalled-app restriction.** Your app lives in your org; a customer authorizing it has not *installed* it.
Since early September 2025:

- **New users cannot authorize an uninstalled app** unless they hold **Approve Uninstalled Connected Apps** (System
  Administrators have it by default; it must be added to other profiles deliberately).
- Users who authorized before the change keep working — unless the app uses the **device flow**, which is blocked
  outright for uninstalled apps.

So a connector that "worked for years" can start failing for *new* customers only. The fixes are customer-side, and
the skill's job is to say which one applies: have the customer's admin install the app in their org (packaged
distribution makes this possible), or have them grant the permission to the authorizing user. Salesforce's own ISV
guidance is to get customers to install the app proactively.

## 8. Verify end-to-end

Authorizing in the org that owns the app proves almost nothing — that org has the app installed by definition. Test
the path a customer takes:

1. Authorize from a **second org** (a sandbox, or a fresh Developer Edition org) through your platform's real connect
   flow, with a non-admin user if customers will use one.
2. Confirm the token response carries a **refresh token** and an **`instance_url`**, and that API calls use the
   `instance_url` host.
3. Force a **refresh**, then force a **second** refresh using the token returned by the first — that is what catches a
   client that ignores rotation.
4. Confirm the PKCE round trip actually happened, rather than assuming the library did it.

| Symptom | Cause |
| --- | --- |
| `redirect_uri_mismatch` | Callback URL not registered exactly (§4) |
| `invalid_client_id` | Wrong consumer key, or authorizing against the wrong host (`login` vs `test` vs My Domain) |
| New customers cannot authorize, existing ones fine | Uninstalled-app restriction (§7) |
| Second refresh fails | Client is not storing rotated refresh tokens (§7) |
| Connection dies after a quiet month | 30-day absolute TTL (§7) |
| Auth succeeds, every API call 404s | Client ignored `instance_url` (§6) |
| `invalid_grant` from one customer only | Their session policy, IP restrictions, or the user's API access |

## 9. Hand off — never commit the secret

- **Do not** write the consumer secret into source control, a test, a fixture, a committed `.env`, a ticket, a PR
  body, or a chat channel. Values go to the user, for the secret store or console.
- If a code change is needed (a callback host, a scope, PKCE support), keep it secret-free and say what the human must
  set out of band.
- Close with: app name, type (External Client App), and owning org; consumer key; where the secret was delivered; the
  authorize and token endpoints; the exact scope strings; the state of each enforced control and who owns the
  remaining work; the customer install story; and anything left for the user to do.

## Stop and ask

Hand back to a human rather than guessing when: the client does not implement PKCE or rotated refresh tokens (report
it — do not register and hope); IP binding requires infrastructure decisions about static egress; the fix requires
customers to install a package or change profile permissions; someone proposes migrating a live Connected App to an
ECA (that re-authorizes every customer); an AppExchange listing or security review asks for compliance, legal or
volume claims; or Setup does not match the **Platform state** section above.

## References

- External Client Apps — https://help.salesforce.com/s/articleView?id=xcloud.external_client_apps.htm&language=en_US&type=5
- Configure ECA OAuth settings — https://help.salesforce.com/s/articleView?id=xcloud.configure_external_client_app_oauth_settings.htm&language=en_US&type=5
- Connected Apps vs External Client Apps — https://help.salesforce.com/s/articleView?id=xcloud.connected_apps_and_external_client_apps_features.htm&language=en_US&type=5
- New Connected Apps can no longer be created (Spring '26) — https://help.salesforce.com/s/articleView?id=005228017&language=en_US&type=1
- Connected app usage restrictions (uninstalled apps) — https://help.salesforce.com/s/articleView?id=005132365&language=en_US&type=1
- Security controls post-enforcement guidance for partners — https://help.salesforce.com/s/articleView?id=005388177&language=en_US&type=1
- Enabling PKCE for OAuth — https://help.salesforce.com/s/articleView?id=005316703&language=en_US&type=1
- OAuth 2.0 web server flow — https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_oauth_web_server_flow.htm&language=en_US&type=5
- OAuth tokens and scopes — https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_oauth_tokens_scopes.htm&language=en_US&type=5
- REST API: authorization through ECAs/connected apps — https://developer.salesforce.com/docs/platform/api-rest/guide/intro-oauth-and-connected-apps.html
- Developer Edition signup — https://developer.salesforce.com/signup
