Preparing for the SOAP API Login() Retirement

Cesar Parra
April 3, 2026

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.

 

What’s Happening

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.

 

Why This Matters for You

 

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.

 

Finding What Uses login()

Salesforce gives you a few angles to approach this. The quickest  starting point is Login History in Setup. Look for entries where:

  • Login Type is Other Apex API or Partner Product
  • Login Subtype is SOAP API

That 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.

What to Migrate To

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.

Cesar Parra
April 3, 2026

Related Articles