In 2025, Salesforce customers got hit by a wave of credential theft attacks, not through zero-days or cryptographic exploits, but through phone calls.
What they did was simple: impersonate IT support, convince an employee to download what appeared to be Salesforce Data Loader, walk them through authorizing a malicious connected app, and then quietly drain the org. The damage was extensive, hitting household names like Google, Adidas, and Chanel.
But what really stings is that Salesforce itself was never breached. The platform worked exactly as designed. Users voluntarily handed over access because nothing in the default configuration stopped them from doing so.
Salesforce has been slowly tightening things up ever since. In September 2025, they restricted access to what are called “uninstalled connected apps”: apps not explicitly installed by a System Administrator, but instead authorized directly by an end user via an OAuth page. No admin involvement required. The attackers exploited this via OAuth 2.0 Device Flow, an auth mechanism designed for devices with limited input capabilities, which made it trivially easy to walk someone through an authorization over the phone. Salesforce blocked Device Flow for uninstalled apps entirely, and new users are now blocked from accessing any uninstalled app until an admin explicitly installs it.
Those were reactive changes, plugging the exact exploit that got people burned. But Salesforce is also making a longer-term, more fundamental shift: moving everything toward OAuth and External Client Apps, and away from the older credential-passing patterns that have always been the weak link.
Now it’s SOAP API’s turn.
Salesforce has published an official article on the retirement. The short version: the SOAP API login() operation is being retired with the Summer ‘27 release for API versions 31.0 through 64.0. It is already unavailable in API versions 65.0 and higher, and it is disabled by default in newly created orgs.
The login() operation is one of the oldest ways to authenticate with Salesforce. You make a SOAP call, pass a username and password directly in the request body, and get back a session ID. That session ID is then used for subsequent API calls.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:partner.soap.sforce.com"><soapenv:Header></soapenv:Header><soapenv:Body><urn:login><urn:username>username</urn:username><urn:password>password</urn:password></urn:login></soapenv:Body></soapenv:Envelope>
It works, it’s pretty simple, and that’s pretty much the problem.
Credentials passed directly over the wire (even over HTTPS) offer no scope limitations, no revocation mechanism tied to a specific integration, and no meaningful audit trail beyond “this user logged in.” When an attacker gets hold of a username and password, they can call login() and have full access indistinguishable from the legitimate application. There is no way to revoke just that session without also locking out the actual user.
If your org is old enough (and most production orgs are), you are almost certainly still running something that uses login(). It might be a custom integration you wrote years ago. It might be a third-party package that has been quietly authenticating this way since it was installed. It might be a CI/CD job that has been running so reliably that nobody has touched it in years.
The thing about authentication code is that it works until it doesn’t. The first sign of a problem will be a complete authentication failure with no graceful fallback.
Summer ‘27 is not tomorrow, but a migration off login() requires identifying every affected integration first, and that turns out to be a surprisingly hard problem.
Salesforce gives you a few angles to approach this. The quickest starting point is Login History in Setup. Look for entries where:
Other Apex API or Partner ProductSOAP APIThat will show you which users these requests are authenticating as. From there, talk to the humans behind those usernames to figure out what application is making the call.
For a more automated approach, the API Total Usage EventLogFile covers the last 24 hours of API events (or 30 days with Event Monitoring). You can pull it with the Salesforce CLI:
sf data query -q "SELECT Id, LogFile, EventType, CreatedDate FROM EventLogFile WHERE EventType IN ('ApiTotalUsage')" -o <your-username>
Once you have the log files, look for rows where:
CONNECTED_APP_ID is empty (meaning no connected app or external client app was used, which is a red flag for login() or raw session ID usage)API_FAMILY is SOAP and API_RESOURCE is login The CONNECTED_APP_ID being empty doesn’t guarantee SOAP login(). It also covers direct session ID usage. But anything in that category is worth investigating, since it means the integration isn’t using the auth model Salesforce is moving toward.
Salesforce wants everything to go through External Client Apps with OAuth. The two flows they recommend:
OAuth Client Credentials Flow: for server-to-server integrations, background jobs, and anything that doesn’t involve a human logging in interactively. The app authenticates with a client ID and client secret (or a certificate), and gets back an access token. No user credentials involved. This is the right choice for most automated integrations.
OAuth Web Server Flow: for user-facing apps where a real human is authenticating. The user gets redirected to Salesforce’s login page, authorizes the app, and is sent back with an authorization code that the server exchanges for a token. Standard OAuth 2.0 dance.
The security improvement over raw login() is concrete: access tokens are scoped to what the connected app allows, they can be revoked independently of the user’s credentials, and they show up with a CONNECTED_APP_ID in the logs, so you can actually track which integration is doing what. With Client Credentials specifically, your integration’s credentials are completely decoupled from any Salesforce user account.
If you’re dealing with a third-party package or tool that still uses login(), the action item is simple but possibly unpleasant: contact the vendor. If they haven’t published a version that uses OAuth by now, push them on it. Summer ‘27 is the deadline, and a vendor who hasn’t started this work is telling you something about how they approach maintenance.
The broader move Salesforce is making is not subtle. Killing Device Flow, restricting uninstalled connected apps, disabling login() by default in new orgs, retiring it entirely: these are all pieces of the same shift. The 2025 attacks weren’t technically sophisticated, but they didn’t need to be, because the platform’s default configuration made them possible. Salesforce is systematically making the insecure option harder to choose.
That’s the right call, even if the migration work it creates is genuinely inconvenient. An API that passes credentials directly in the request body has no place in a modern integration architecture. Summer ‘27 is enough runway to do this properly, but probably not enough to justify putting it off.