Platform
The connected app is the new attack surface: audit your OAuth tokens
The 2025 Salesforce breaches didn't crack a password. They stole OAuth refresh tokens from a third-party app and walked in with valid, MFA-exempt access. Here's how to inventory every connected app, scope what its tokens can reach, and revoke the one you don't recognize.
The most expensive Salesforce breaches of the last two years never touched a password. Attackers compromised a third-party vendor, stole the OAuth refresh tokens its app held for customer orgs, and logged in as a trusted integration. Valid token, valid session, no MFA prompt, because a refresh token the org already approved doesn’t trigger one. Then they ran SOQL until they found AWS keys in support cases.
The Salesloft/Drift campaign in August 2025 did this across roughly 760 orgs and pulled around 1.5 billion records. A later wave hit Gainsight-connected apps the same way. Microsoft and the Cloud Security Alliance both mapped it as a year-long pattern. The weak link was never a Salesforce vulnerability. It was a supply-chain one: an OAuth grant you made to someone else’s software and never looked at again.
So the question for your org is simple: which connected apps hold live tokens right now, what can each reach, and would you notice if one behaved like an attacker? Here’s how to answer all three.
A connected app you approved once is a standing key
Every time a user authorizes an external app, or an admin pre-authorizes one, Salesforce issues that app tokens. The access token is short-lived. The refresh token, by default, is valid until someone revokes it.
That’s the part that makes a connected app a standing key rather than a session. The app can mint fresh access tokens indefinitely, as the user, from anywhere the OAuth policies allow, until you take the grant away.
That design is fine when the app is trustworthy and the grant is scoped. It turns into the 2025 story when the app holds a broad scope, the refresh token never expires, there’s no IP restriction, and nobody is watching. The token does exactly what it was authorized to do. The only thing wrong is who’s holding it.
Governance here is not exotic. It’s four questions asked of every connected app, and most orgs have never asked them even once.
Inventory: what holds a token right now
Start where the live grants live, not the connected-app definitions. Setup has a page called Connected Apps OAuth Usage. It lists every app with active tokens, how many users authorized it, and a one-click action to revoke the app’s access for the whole org. That page, not the app list, is your inventory.
Walk it and sort every row into one of three buckets:
- Apps you recognize and still use. Leave them, but scope them (next section).
- Apps you recognize and no longer use. Revoke them from this page in one action.
- Apps you don’t recognize. These are the ones that matter. A connected app nobody can name, holding tokens for a dozen users, is either shadow IT or the thing you’ll read about later.
For a specific user’s grants, each user’s detail page has an OAuth Connected Apps section where you can see and revoke individual tokens, and users can revoke their own from personal settings. For a clean cutover, you can also revoke tokens programmatically through the revoke endpoint, which is how you script a bulk rotation after an incident.
One trap to know: a connected app can allow a maximum of 5 access-token and refresh-token pairs per user. When a sixth is requested, Salesforce revokes the oldest pair. That’s usually invisible, but it means a user hammering re-auth across tools can invalidate a token an integration was relying on, which looks like a random 401 with no obvious cause.
You can’t revoke what you never inventoried. The connected app you can’t name is the audit finding, every time.
Scope: shrink what a stolen token can do
A token is only as dangerous as the access behind it, and the 2025 attackers got so much because the grants were broad. Three levers shrink the blast radius, and all three live on the connected app’s OAuth policies.
Minimise the scopes. OAuth scopes decide what the token can do: full grants everything the user can do, api grants API access, refresh_token (offline access) is what makes the token long-lived, web allows web access.
Most integrations need api and nothing close to full. Audit each app’s scopes and cut full wherever a narrower scope works. An app that only reads Accounts does not need the keys to the org.
Lock down who can authorize it. Set Permitted Users to “Admin approved users are pre-authorized” and grant access through a permission set or profile, so a random user can’t connect a new tool on their own.
One sharp edge: the “Use Any API Client” permission bypasses that restriction entirely, letting a user reach any connected app regardless of its Permitted Users setting. Audit who holds that permission the same day you lock down the apps, or the lockdown is theatre.
Enforce IP restrictions. Connected apps default to relaxing IP restrictions. For a server integration that calls from known addresses, switch the OAuth policy to enforce IP restrictions and add the integration’s egress IPs to the app’s allowlist, or to Login IP Ranges on the user.
A stolen token used from an attacker’s infrastructure then fails the IP check even though the token itself is valid. That’s the same discipline the Hyperforce migration forces on your network allowlists anyway, applied to the identity layer instead of the network layer.
While you’re in the policies, set the refresh token to expire. “Valid until revoked” is the convenient default and the reason a stolen token worked for weeks. An idle-timeout or an absolute expiry means a token that stops being used stops working, which is exactly what you want for a grant you’re not actively watching.
The newer model: External Client Apps
If you’re setting up integrations today, Salesforce is steering new work toward External Client Apps rather than classic connected apps. They cover the same OAuth flows with a cleaner separation between the app’s global settings and the per-org policies, and they package and version more predictably when an ISV ships an app into many orgs.
The governance questions don’t change: scope, permitted users, IP, token lifetime, and inventory are the same five levers. If your estate is a mix of both, govern them together, and don’t let the newer object become a second blind spot next to the connected apps you finally audited.
For the specific case of an AI agent or MCP client acting in your org, there’s a better pattern than a standing integration token at all. OAuth token exchange lets the agent present the end user’s token and get back a short-lived one scoped to that user, so the audit trail names a person and nothing holds a blanket, long-lived grant. Where you can use it, prefer it to a connected app with a forever refresh token.
Close the loop with monitoring
Scoping and revocation shrink the attack surface. They don’t tell you when a token that survived your audit starts misbehaving. That’s the job of Real-Time Event Monitoring: the ApiAnomalyEvent detector exists precisely to flag a connected app’s token suddenly pulling ten times its normal volume, and a Transaction Security Policy can block or alert on it in the moment.
Governance decides what a token may reach; monitoring catches the one that’s reaching for more than it should.
The third piece is the one the retirement project hands you anyway. Moving integrations off the username-password OAuth flow replaces stored passwords with scoped, token-based identities, and that migration is the natural moment to also right-size the scopes and IP policies on the connected app each integration uses. Do them in one pass.
A one-week audit you can run now
You don’t need a program for the first pass. You need an afternoon on the OAuth Usage page and a short list of decisions:
- Open Connected Apps OAuth Usage and list every app with active tokens.
- Revoke every app you don’t recognize or no longer use, today.
- For each app you keep, check its scopes and cut
fulldown to what it needs. - Set Permitted Users to admin-approved, and audit who holds “Use Any API Client”.
- Enforce IP restrictions and a refresh-token expiry on every server integration.
- Turn on monitoring for the API-anomaly signal so the next audit is continuous, not annual.
Run that, and the 2025 playbook stops working on your org. Not because the vendor can’t be breached, but because the grant you gave them is small, expiring, IP-bound, and watched, which is the difference between a vendor incident that’s an inconvenience and one that becomes your breach. For regulated teams, this is table stakes; it’s the same posture our financial-services clients already answer to an auditor for.
Understanding the basics
What is a connected app in Salesforce, and why is it a security risk?
A connected app is how an external application authenticates to your org through OAuth. When a user authorizes it, Salesforce issues tokens, including a refresh token that by default is valid until revoked. That standing grant is a risk because anyone who steals the token can act as the authorized user with valid, often MFA-exempt access until you revoke it.
How do I see which connected apps have access to my org?
Go to Setup and open Connected Apps OAuth Usage. It lists every app with active tokens, how many users authorized it, and lets you revoke an app’s access for the whole org in one action. Individual user grants appear on each user’s detail page under OAuth Connected Apps.
How do I revoke a connected app’s OAuth tokens?
From the Connected Apps OAuth Usage page you can revoke an app’s access org-wide. From a user’s detail page you can revoke that user’s individual tokens, and users can revoke their own from personal settings. For bulk or scripted revocation, use the OAuth revoke endpoint.
What scopes should a connected app have?
The least that its job requires. Most integrations need the api scope and the refresh_token scope for offline access, not full. Granting full gives a token everything the user can do, which is what makes a stolen one so damaging. Audit and cut scopes per app.
What’s the difference between a connected app and an external client app?
Both configure OAuth access for an external application. External Client Apps are the newer model Salesforce steers new work toward, with a cleaner split between global settings and per-org policies and better packaging for ISVs. The governance levers, scope, permitted users, IP, token lifetime, and inventory, are the same for both.
If you’ve never walked your Connected Apps OAuth Usage page and can’t say what holds a token into your org, that audit is a fast, high-value engagement, and exactly the kind of work we do. Talk to us.