🔑 Key Points
- The Integration Contract: Every external system calling Salesforce REST, SOAP, Bulk, or Streaming APIs via OAuth 2.0 authenticates through a Connected App or an External Client App.
- Intersection of Access: OAuth 2.0 scopes cap what the application's token is allowed to request (for example,
api,web, orrefresh_token). Profiles and Permission Sets cap what the authenticated user is allowed to do. Effective access is always the strict intersection of both. - Admin Pre-Approval vs. Self-Authorization: Setting an app's policy to All users may self-authorize allows any user to approve third-party tools via OAuth consent screens, creating unmonitored refresh tokens. Setting it to Admin approved users are pre-authorized restricts access to explicitly assigned Profiles and Permission Sets.
- The Shift to External Client Apps (ECAs): External Client Apps modernize the Connected App model by locking access by default until a Permission Set or Profile is assigned, cleanly separating developer metadata from subscriber admin policies, supporting Second-Generation Managed Packaging (2GP), and omitting legacy insecure flows like the OAuth 2.0 Username-Password and Implicit flows.
- Org-Wide Governance: Features like Connected Apps OAuth Usage and API Access Control let administrators audit standing tokens and block unapproved third-party applications from calling Salesforce APIs altogether.
🔍 Concept: How Connected Apps and External Client Apps Govern Access
Whether you are integrating an enterprise middleware platform, a mobile app, or an AI agent, Salesforce needs a structured declaration of trust before issuing an access token. Historically, that declaration has been a Connected App. Today, Salesforce provides a second, modernized generation of that contract called an External Client App (ECA).
1. The Three Pillars of a Connected App
A classic Connected App combines developer definitions and administrator governance into three areas:
- Credentials and Flows: When you enable OAuth settings on a Connected App, Salesforce generates a Consumer Key (
client_id) and a Consumer Secret (client_secret), or lets you upload an X.509 digital certificate for the OAuth 2.0 JWT Bearer flow. These credentials identify the external client application during the authentication handshake. - OAuth Scopes (Token Boundaries): Scopes define which functional doors the issued access token can open. Common Salesforce scopes include
api(access to REST, SOAP, and Bulk APIs, but not the browser UI),web(ability to use the token in a browser session viafrontdoor.jsp),refresh_token/offline_access(ability to obtain a new access token when the current one expires without human interaction),openid(access to OpenID Connect identity claims), andfull(encompasses all permissions the user possesses, excludingrefresh_token, which must always be requested explicitly). - Admin Policies (User and Session Boundaries): Once installed or created, the Connected App's Manage page controls operational policies: Permitted Users (self-authorization vs. admin pre-approval), IP Relaxation (whether to enforce the user's Profile IP restrictions or relax them for this app), Session Timeout values, and Refresh Token expiration policies (for example, valid until revoked vs. expiring after a set period of inactivity).
2. Understanding the Intersection: Scopes vs. User Permissions
A frequent point of confusion during architecture reviews is how OAuth scopes interact with Salesforce object, field, and system permissions. Keep two rules front and center:
- Scopes bound the token: If an integration user has System Administrator permissions, but the Connected App is only granted the
chatter_apiscope, a token issued through that Connected App cannot queryAccountrecords via the standard REST API. The token simply cannot invoke endpoints outside its granted scope. - User permissions bound the user: Conversely, granting the
fullorapiscope to a Connected App never elevates a user's privileges. If the authenticating user has Read-Only access to theOpportunityobject on their Profile and Permission Sets, a token withfullscope still cannot update or delete anOpportunity. Effective access is always the overlap—the mathematical intersection—between what the token scope permits and what the user's security model permits.
3. The Evolution to External Client Apps (ECAs)
Classic Connected Apps served Salesforce well for over a decade, but they carried structural pain points: packaging them in Second-Generation Managed Packages (2GP) was cumbersome, developer configuration and admin policies were tangled together, and a newly created Connected App defaulted to All users may self-authorize unless an administrator remembered to lock it down.
External Client Apps (ECAs) address these architectural gaps directly:
- Secure by Default: When you create an External Client App, no user can authenticate against it until an administrator explicitly grants access via Permission Sets or Profiles. There is no accidental "open to the whole org" default state.
- Clean Separation of Concerns: ECA metadata is split into distinct files—such as a header/global OAuth file managed by the developer and a separate policies file (
ExtlClntAppOauthPolicies) managed by the subscriber org administrator. - Removal of Legacy Insecure Flows: ECAs intentionally omit support for the legacy OAuth 2.0 Username-Password flow and the Implicit grant flow, guiding teams toward modern patterns like the OAuth 2.0 Client Credentials flow, JWT Bearer flow, or Authorization Code flow with PKCE (Proof Key for Code Exchange).
- 2026 Platform Direction: As indicated in your architectural notes for 2026 (including Spring '26 updates and modern AI integrations like Salesforce's Hosted Model Context Protocol [MCP] Server), Salesforce is actively steering all new integration development toward External Client Apps, restricting default creation of classic Connected Apps in newer org setups while keeping existing Connected Apps functional for legacy and specialized features (such as mobile push notifications).
🎬 Scenario Walkthrough: Auditing Self-Authorized "Shadow" Integrations
Consider a common enterprise security review: an organization has enforced Single Sign-On and Multi-Factor Authentication at the front door, assuming their Salesforce org is tightly locked down. However, when the architect opens Connected Apps OAuth Usage in Setup, they discover 35 unfamiliar third-party apps—ranging from browser extensions to free data-export utilities—holding active, standing refresh_token grants authorized by individual sales reps over the past four years.
Why This Happens: Whenever a user clicks "Allow" on an OAuth consent screen for an uninstalled third-party app, Salesforce dynamically registers that app on the Connected Apps OAuth Usage page. Because the default org setting allows users to self-authorize uninstalled apps, any user with the API Enabled permission can grant a third-party vendor long-lived offline access to every record that user can see.
How to Fix and Govern It:
- Inventory Active Grants: Navigate to Connected Apps OAuth Usage in Setup and click the User Count number next to each app to see which users hold active tokens, when they last used them, and how many times the token was refreshed.
- Install and Lock Down Legitimate Apps: Click Install next to approved third-party apps on the OAuth Usage page so you can edit their policies, change Permitted Users to Admin approved users are pre-authorized, and assign them only to specific Permission Sets.
- Revoke and Block Unapproved Apps: Click Revoke to terminate existing sessions and refresh tokens for unauthorized tools, or click Block to prevent users from re-authorizing that specific app.
- Enable API Access Control: For comprehensive protection, enable API Access Control in Setup (or request it via Salesforce Support if not visible in your org's Setup menu) and select For admin-approved users, limit API access to only allowlisted connected apps. This blocks all uninstalled self-authorized apps by default.
🛠️ Worked Example: Auditing OAuth Token Grants and Comparing Metadata
To govern integrations effectively, you need to know both how to inspect active OAuth tokens currently living in your org and how an External Client App's metadata structure enforces least privilege. Below are two illustrative examples you can adapt and test in a sandbox environment.
Part 1: Querying Active OAuth Tokens (OauthToken) via SOQL
Besides clicking through the Setup UI, administrators with the Modify All Data or Manage Users permission can query the standard OauthToken object using Developer Console, Workbench, or Salesforce CLI to find stale or high-use third-party authorizations. Note that this query requires testing in your environment and only returns tokens the running admin has visibility to inspect.
SELECT Id, AppName, UserId, User.Username, UseCount,
CreatedDate, LastUsedDate, AccessToken, DeleteToken
FROM OauthToken
WHERE LastUsedDate < LAST_N_DAYS:90
ORDER BY UseCount DESC
LIMIT 100
How to interpret and verify the results:
AppNameidentifies the Connected App or External Client App holding the grant.UseCountshows how many times the client has used or refreshed the session.LastUsedDate < LAST_N_DAYS:90isolates standing tokens that have not been used in over 90 days—prime candidates for revocation via the Connected Apps OAuth Usage UI or by configuring an automatic Expire refresh token if not used for n days policy on the app.
Part 2: Illustrative External Client App OAuth Policy Metadata
When you deploy an External Client App via Salesforce DX (Metadata API), the administrator policy file (.ecaOauthPolicies-meta.xml) is stored separately from the developer's OAuth configuration file. Notice how the excerpt below enforces admin pre-approval (AdminApprovedUsersArePreAuthorized) and sets a strict 30-day inactivity timeout on refresh tokens:
<?xml version="1.0" encoding="UTF-8"?>
<ExtlClntAppOauthPolicies xmlns="http://soap.sforce.com/2006/04/metadata">
<externalClientApplication>Partner_Order_Sync_ECA</externalClientApplication>
<ipRelaxation>Enforce</ipRelaxation>
<permittedUsersPolicyType>AdminApprovedUsersArePreAuthorized</permittedUsersPolicyType>
<refreshTokenPolicyType>ExpireRefreshTokenAfterInactivity</refreshTokenPolicyType>
<refreshTokenValidityPeriod>30</refreshTokenValidityPeriod>
<refreshTokenValidityUnit>Days</refreshTokenValidityUnit>
<requiredSessionLevel>STANDARD</requiredSessionLevel>
</ExtlClntAppOauthPolicies>
Why this structure matters: Even if a developer packages and updates the External Client App in a future release, this policy metadata remains under the subscriber org administrator's control. And because permittedUsersPolicyType defaults to pre-authorized in ECAs, no user can obtain a token until you explicitly link a Permission Set to this External Client App in Setup.
🧭 360 Card — Connected Apps, Scopes & Policies
Rule: An OAuth token's effective authority is the app's granted scopes intersected with the authenticating user's permissions—never either layer by itself.
Benefit: A single, auditable contract declares which OAuth flows an external system may use, which scopes it can request, which users can authenticate, and what IP or session timeouts apply.
Tradeoff: When self-authorization is left open, users quietly approve third-party apps over the years, creating an unowned inventory of standing refresh tokens with broad API access.
Boundaries & Limits: Scopes cap what the client application may request; Profiles and Permission Sets cap what the user record can do. Granting the full scope never gives a user object or field access they do not already possess. External Client Apps (ECAs) are the modern successor to Connected Apps, and newer org configurations restrict creating classic Connected Apps by default to steer teams toward ECAs.
Anti-Pattern — Granting full Scope to Save Time: Selecting full scope makes an integration work immediately without checking which endpoints it actually needs, but it expands the blast radius of a leaked token and signals to security auditors that least privilege was skipped.
Migration Strategy: Migrate existing Connected Apps to External Client Apps by risk and ownership—move internal unpackaged integrations first, leave vendor-managed (ISV) packages to the vendor's upgrade timeline, and build every new integration as an External Client App from day one.
full and refresh_token scopes to every app. If an integration user has Modify All Data and an app holds the api scope, any compromised token from that app can read, modify, or delete your entire database via REST or Bulk API. Always pair minimal OAuth scopes (api only, no web or full unless required) with a dedicated Salesforce Integration license user granted only the exact object and field permissions required via Permission Sets.
❓ Core Q&A and Architecture Scenarios
full scope everywhere out of convenience.
A: Think of OAuth authorization as passing through two sequential gates:
- Gate 1 (The Scope Gate): Does the access token have permission to call this category of Salesforce resource? For example, a token issued with only the
apiscope can invoke REST and SOAP endpoints, but if someone tries to pass that token tofrontdoor.jspto open a browser UI session, Salesforce blocks it because theweborfullscope is missing. - Gate 2 (The User Permission Gate): Once inside the API, what can the underlying
Userrecord actually touch? Anapiscope token running as a Read-Only user can only read records. Even if you grant thefullscope to that Read-Only user's token, the token still cannot insert or delete records. - Where Teams Fail in Both Directions:
- Over-scoping the App: Developers often select
fullandrefresh_tokenon every Connected App "just so we don't hit permission errors." If that token leaks, an attacker can use it not only for API calls, but also to launch interactive browser sessions if the user has UI access. - Over-permissioning the User: Conversely, teams sometimes assign the System Administrator profile to an integration user, assuming that a narrow OAuth scope keeps the org safe. In reality, the standard
apiscope exposes the entire data model and metadata APIs that the System Administrator profile can access.
- Over-scoping the App: Developers often select
- The Right Practice: Assign minimal scopes per app, use a dedicated integration user with minimal Permission Set access, enforce Admin approved users are pre-authorized, and align refresh-token expiration policies with how securely the external client stores credentials.
A1: Governing historical "shadow IT" integrations requires a structured four-step rollout so you do not accidentally break business-critical tools on a Monday morning:
- Step 1 — Inventory via OAuth Usage and Login History: Start at Connected Apps OAuth Usage in Setup and cross-reference with Login History (filtering by Application and Login Type such as Remote Access 2.0). Identify every uninstalled app holding standing refresh tokens, which departments use them, and when they were last active.
- Step 2 — Triage and Install Legitimate Apps: For approved tools (such as an authorized e-signature or calendar plugin), click Install on the Connected Apps OAuth Usage page. Installing the metadata shell lets you manage its policies locally. Switch Permitted Users from self-authorized to Admin approved users are pre-authorized and assign access via a dedicated Permission Set.
- Step 3 — Revoke Stale and Risky Grants: For abandoned tools or unapproved browser plugins, click Revoke to kill active sessions and refresh tokens immediately, and click Block to prevent re-authorization.
- Step 4 — Flip the Org Default with API Access Control: Once legitimate apps are pre-authorized, enable API Access Control to restrict all API access strictly to allowlisted, admin-approved Connected Apps and External Client Apps. Communicate the cutoff date to users ahead of time so they know where to request approval for new productivity tools.
client_id) was accidentally committed to a public GitHub repository. What is your blast-radius analysis and incident response?A2: Separate your analysis into what leaked (Consumer Key alone vs. Consumer Secret/Private Key) and which OAuth flows are enabled on that app:
- Blast-Radius Analysis:
- In server-to-server flows like Client Credentials or JWT Bearer, or confidential Web Server (Authorization Code) flows that require a secret, the Consumer Key (
client_id) is a public identifier—only half of the credential. By itself, an attacker cannot mint a token without the corresponding Consumer Secret or private X.509 signing key. - However, if the app is configured as a public client without PKCE (Proof Key for Code Exchange) required, or if it allows the legacy Implicit or Username-Password flows, a leaked Consumer Key lowers the barrier for attackers to craft convincing OAuth phishing links or attempt credential-stuffing attacks against your token endpoint.
- Furthermore, where a Consumer Key is hardcoded in source code, the Consumer Secret is frequently sitting in the same commit history or an adjacent config file. Always treat a public repo leak as a potential full credential compromise until proven otherwise.
- In server-to-server flows like Client Credentials or JWT Bearer, or confidential Web Server (Authorization Code) flows that require a secret, the Consumer Key (
- Incident Response Playbook:
- Rotate Credentials Immediately: In Salesforce Setup, rotate the Consumer Secret (or generate a new External Client App / Connected App with a fresh Consumer Key and Secret) and update the secret in your integration's secure vault.
- Revoke Active Tokens: Go to Connected Apps OAuth Usage and revoke all active access and refresh tokens for that application to terminate any sessions established during the exposure window.
- Audit Logs for Anomalous Activity: Inspect Login History and Event Monitoring (
ApiEvent/LoginEvent) for any token exchanges, unfamiliar source IPs, or bulk queries executed under that Consumer Key since the commit timestamp. - Harden the Pipeline and App Policy: Enforce Require Proof Key for Code Exchange (PKCE) and Require Secret for Web Server Flow on the app, enforce IP ranges on the integration user's Profile, and add pre-commit secret scanning (such as GitLeaks or GitHub Secret Scanning) to your repository.
A3: A safe migration avoids a "big-bang" cutover and prioritizes apps by ownership, flow compatibility, and risk:
- Phase 1 — Inventory and Categorize by Ownership: Export your active integrations from the Connected Apps OAuth Usage page and Setup list. Separate them into two buckets: Internal/Custom Integrations (owned by your team) and ISV/Managed Package Apps (owned by third-party vendors). You do not migrate a vendor's packaged Connected App yourself—you track the vendor's package upgrade schedule.
- Phase 2 — Remediate Blocked OAuth Flows First: Identify any custom Connected Apps still relying on the legacy Username-Password flow or Implicit flow. Because External Client Apps do not support those insecure flows, your integration team must refactor those clients to use the OAuth 2.0 Client Credentials flow or JWT Bearer flow before migrating the app container.
- Phase 3 — Identify Feature Exceptions: Check if any Connected App relies on specialized capabilities that External Client Apps are still expanding to cover (for example, specific mobile push notification configurations). Keep those on classic Connected Apps temporarily with a scheduled quarterly review.
- Phase 4 — Phased Cutover and New-Build Mandate: Mandate that every net-new integration—especially modern AI and agentic integrations such as Salesforce's Hosted MCP Server—is built as an External Client App from day one. Migrate internal custom integrations one by one in a sandbox, updating the external system's
client_idandclient_secretduring a planned maintenance window, and retire the old Connected App once traffic drops to zero.
A4: In any new org or integration design in 2026, External Client Apps are the primary standard. Even if an administrator can toggle a setting to re-enable classic Connected App creation, treating ECAs as your default gives you closed-by-default security, clean separation between developer metadata and admin policies, safer secret rotation, and compatibility with newer platform capabilities. Reserve classic Connected Apps strictly for maintaining legacy setups until they are migrated.
🧪 Self-Check Question: An integration developer creates a new External Client App in a sandbox, configures the OAuth 2.0 Client Credentials flow, and links an active Integration User to the run-as policy. When the middleware attempts to request an access token, Salesforce rejects the request with an authorization error. The Consumer Key and Consumer Secret are verified to be 100% correct. What setup step did the team most likely forget?
Answer & Explanation: Unlike classic Connected Apps—which historically defaulted to All users may self-authorize—an External Client App is closed by default. Even with valid credentials and a configured flow, no user can obtain a token until an administrator explicitly grants access to the External Client App via a Permission Set (or Profile) assigned to that integration user. Adding the ECA to the integration user's Permission Set resolves the error.
🔗 Next Concept Follow-Up: Choosing the Right Server-to-Server OAuth Flow
A: Both flows are built for headless server-to-server integrations and both are supported by External Client Apps, but they differ in how credentials are exchanged and how the running user is determined:
- Choose the OAuth 2.0 JWT Bearer Flow when: You want certificate-based authentication where no shared secret is ever transmitted over the network, or when a single middleware app needs to dynamically impersonate different pre-authorized Salesforce users by changing the
sub(subject username) claim inside a digitally signed JSON Web Token. You upload the public X.509 certificate to the External Client App, and your middleware signs the JWT using its private key. - Choose the OAuth 2.0 Client Credentials Flow when: Your external system natively supports standard OAuth 2.0
client_idandclient_secretauthentication out of the box (without custom JWT-signing code) and always runs under a single dedicated integration user. In Salesforce, you specify the Run As integration user directly inside the app's policies, and Salesforce exchanges the Client ID and Client Secret directly for anaccess_token—never issuing arefresh_tokenbecause the client can simply request a fresh access token whenever needed.
📚 Official Salesforce Documentation
To review current release specifications, metadata types, and setup instructions, consult these primary Salesforce resources:
- Review External Client Apps in Salesforce Help for architecture comparisons with Connected Apps, packaging rules, and permission set policies.
- Consult Connected Apps Overview in Salesforce Help for OAuth 2.0 scope definitions, IP relaxation settings, and session policies.
- Refer to Configure a Connected App or External Client App for the OAuth 2.0 Client Credentials Flow for server-to-server setup steps.
Comments
Questions, corrections and your own experience are all welcome.
Post a Comment