INVALID_LOGIN: Invalid username, password, security token; or user locked out
A login attempt failed. The platform deliberately combines four very different causes into one message — username typo, wrong password, missing/wrong security token, IP-restricted profile — to avoid leaking which one is the actual problem. Diagnosis is process of elimination.
Also seen asINVALID_LOGIN·Invalid username, password, security token·user locked out·INVALID_LOGIN: Invalid username
A new integration engineer follows the runbook to configure a connector. The credentials look right, the security token was copy-pasted from a fresh reset email, and the integration uses the same OAuth library that other systems use. The first login attempt returns INVALID_LOGIN: Invalid username, password, security token; or user locked out. The credentials work fine in a browser. Something is different about how the API sees them.
What the platform is checking
When an API client logs in to Salesforce with username and password, the platform validates three things: the username exists and is active, the password matches, and the security token concatenated to the password matches the user's current token. If the user is locked out by too many failed attempts or by an admin freeze, the same generic error fires.
The error message is deliberately generic. A specific message that distinguishes "wrong username" from "wrong password" from "wrong token" would help attackers enumerate accounts. Salesforce returns one message for the entire class of authentication failures, leaving the integration to figure out which of the four causes applies.
The four causes are easy to confuse because the error is identical for all of them. The integration owner must check each in sequence: is the username correct, is the password correct, is the security token current, and is the user locked out or frozen.
For organizations that use SSO, IP-allowlisting, or trusted IP ranges, the behavior changes. A login from a trusted IP range does not require a security token. A login from outside that range requires the token concatenated to the password. The same user, the same password, the same code can succeed from one server and fail from another.
What actually fails
The username is wrong. Typos, capitalization mismatches (Salesforce usernames are case-sensitive in some legacy contexts), and copy-paste errors that include trailing whitespace are common. A username that looks correct can have a hidden newline or tab character.
The password is wrong. Password rotation cycles invalidate cached credentials. A password change by an admin, a forced reset, or an org-wide password policy refresh all change the live password without notifying integrations.
The security token is stale. Salesforce invalidates the user's security token whenever the password changes. The integration's code may still use the old token. Until the user resets the security token and the integration is updated with the new value, every login fails.
The user is locked out or frozen. Five failed logins within a short window lock the user. An admin can freeze a user without deactivating them. Both states return the same error.
The broken example
A Python integration that posts credentials to the SOAP login endpoint:
import requests
login_url = 'https://login.salesforce.com/services/Soap/u/60.0'
username = 'integration@acme.com'
password = 'SecurePass123!'
security_token = 'aBcDeFgHiJ'
body = f'''
<?xml version="1.0" encoding="utf-8" ?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Body>
<n1:login xmlns:n1="urn:partner.soap.sforce.com">
<n1:username>{username}</n1:username>
<n1:password>{password}{security_token}</n1:password>
</n1:login>
</env:Body>
</env:Envelope>'''
response = requests.post(login_url, data=body, headers={'Content-Type': 'text/xml', 'SOAPAction': 'login'})
print(response.text)
The body concatenates password and security token correctly. The login still fails because:
The security token was reset two weeks ago when the integration user changed their password. The new token never made it into the integration's credential store.
A separate environment (a sandbox copy) uses the same integration code, points to test.salesforce.com, and the sandbox version of the security token differs from production. The deploy script copied the production credential by mistake.
The integration user was frozen by an admin who saw failed login alerts and wanted to stop them while investigating.
A retry loop on the integration kept retrying with the wrong credentials and accumulated enough failures to trigger the lockout. The actual root cause is now compounded by the lockout.
The fix, three paths
Use OAuth with a Connected App instead of username and password. The modern recommendation is the OAuth client-credentials flow or JWT bearer flow. Both eliminate the security token entirely. The integration registers as a Connected App, exchanges its credentials for an access token, and uses the token for subsequent API calls.
import requests
import jwt
import time
def get_access_token():
private_key = open('/secrets/sf-private.key').read()
payload = {
'iss': 'CONNECTED_APP_CONSUMER_KEY',
'sub': 'integration@acme.com',
'aud': 'https://login.salesforce.com',
'exp': int(time.time()) + 300
}
token = jwt.encode(payload, private_key, algorithm='RS256')
res = requests.post(
'https://login.salesforce.com/services/oauth2/token',
data={
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': token
}
)
return res.json()['access_token']
The JWT flow uses a public-private key pair. The private key stays on the integration server. The public certificate is uploaded to the Connected App in Salesforce. There is no password to rotate, no token to regenerate, no lockout cycle to manage.
Reset the security token and update the integration. When using the legacy username-password flow, the user resets their security token (My Settings, Personal, Reset My Security Token, or via Setup, Users, Reset Security Token for the integration user). The new token is emailed. The integration's credential store is updated with the new token and redeployed.
Verify each component in order. When the error fires, work through the four causes systematically:
sfdx force:auth:web:login -u integration@acme.com
The CLI login uses a browser and works without a token. If it succeeds, the username and password are correct, which narrows the issue to the security token or the IP range. If it fails, the username or password is wrong.
curl -d "grant_type=password" \
-d "client_id=$CONSUMER_KEY" \
-d "client_secret=$CONSUMER_SECRET" \
-d "username=$USERNAME" \
-d "password=${PASSWORD}${SECURITY_TOKEN}" \
https://login.salesforce.com/services/oauth2/token
A direct curl call surfaces the exact error message the integration is hitting. Response codes and error descriptions point at the cause.
The fixed example
A Python integration using OAuth JWT bearer flow:
import os
import time
import jwt
import requests
class SalesforceClient:
def __init__(self):
self.consumer_key = os.environ['SF_CONSUMER_KEY']
self.username = os.environ['SF_USERNAME']
self.login_url = os.environ.get('SF_LOGIN_URL', 'https://login.salesforce.com')
self.private_key = open(os.environ['SF_PRIVATE_KEY_PATH']).read()
self.access_token = None
self.instance_url = None
self.expires_at = 0
def authenticate(self):
now = int(time.time())
payload = {
'iss': self.consumer_key,
'sub': self.username,
'aud': self.login_url,
'exp': now + 300
}
assertion = jwt.encode(payload, self.private_key, algorithm='RS256')
res = requests.post(
f'{self.login_url}/services/oauth2/token',
data={
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': assertion
}
)
if res.status_code != 200:
raise RuntimeError(f'Auth failed: {res.text}')
body = res.json()
self.access_token = body['access_token']
self.instance_url = body['instance_url']
self.expires_at = now + 7200
def get(self, path):
if not self.access_token or time.time() > self.expires_at - 60:
self.authenticate()
return requests.get(
f'{self.instance_url}{path}',
headers={'Authorization': f'Bearer {self.access_token}'}
)
The client has no password, no security token, no lockout exposure. The private key file lives on a single secure server. Rotating the integration's access is a matter of replacing the certificate on the Connected App.
Edge cases and gotchas
Sandbox vs production login URLs. Production uses login.salesforce.com. Sandboxes use test.salesforce.com. The integration that worked in production fails in sandbox if the URL is hardcoded. Use the SF_LOGIN_URL environment variable or a parameter.
MFA on integration users. Salesforce Multi-Factor Authentication, required for many users since 2022, blocks the username-password flow entirely. Integration users typically have MFA disabled via a permission set ("Exempt from Multi-Factor Authentication"), but if the exemption was removed, every login fails. OAuth flows that do not use username-password are not affected.
IP allowlist on the profile. A profile-level Login IP Range restricts where the user can log in from. The integration server's IP must fall inside the range. Cloud-hosted integrations on dynamic IPs (Lambda, container schedulers) can move IPs and fall outside the range without warning.
Trusted IP Range vs Login IP Range. Trusted IP Range is at the org level and exempts users from requiring a security token when logging in from inside the range. Login IP Range is at the profile level and refuses login entirely from outside the range. These are different settings with similar names; both can cause the error.
The token in the password field. The SOAP login API concatenates token to password. The OAuth username-password flow does the same, with the token appended after the password value. Forgetting to concatenate is a frequent first-time mistake.
Activation requirement. A newly created user receives an activation email. Until they click the link and set a password, the credentials provisioned by the admin are not yet active. Integration users created through automation must have their first login completed before integrations can authenticate.
Computer activation for new IP addresses. Salesforce can require an activation code for logins from a previously unseen IP. If your integration started using a new server, the first login may need to be verified via the integration user's email. Either the user (or someone with access to that mailbox) needs to click the link, or the IP needs to be in the Trusted IP Range to skip the activation.
Defensive habits
Move all new integrations to OAuth (JWT bearer or client credentials). The username-password flow is supported but is the legacy path. New deployments should not adopt it. Existing integrations should migrate as resourcing allows.
Keep integration credentials in a secrets manager. AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, or HashiCorp Vault keeps the credential out of the codebase and out of unencrypted environment variables. Rotation is a config change in one place.
Document the security-token reset process for each integration user. When the password rotates, the token rotates with it. The runbook should specify who resets, who updates the integration, and how the change is verified.
Lockouts cascade. A misconfigured retry loop can rack up failed logins quickly and trigger the lockout. Implement exponential backoff with a hard cap on retries. After a small number of failures, alert and stop retrying until a human investigates.
Test patterns
A test that verifies authentication succeeds with valid credentials and fails gracefully with invalid ones:
def test_authentication_returns_valid_token():
client = SalesforceClient()
client.authenticate()
assert client.access_token is not None
assert client.instance_url.startswith('https://')
def test_authentication_raises_with_invalid_consumer_key():
os.environ['SF_CONSUMER_KEY'] = 'invalid'
client = SalesforceClient()
with pytest.raises(RuntimeError) as exc_info:
client.authenticate()
assert 'Auth failed' in str(exc_info.value)
Integration tests against a sandbox confirm the credentials and the network path. Unit tests with mocked HTTP responses cover the error handling path.
Diagnosing in production
When the error fires:
- Confirm the username is exact: no trailing whitespace, no case mismatch.
- Try logging into the UI with the same password. If the UI works, password is correct, focus on token and IP.
- Check Setup, Users, the integration user's Login History tab. The most recent failed attempts show source IP and reason.
- Verify the security token has not been reset since the integration was last updated.
- Check whether the user is locked out or frozen. Unfreezing requires admin action.
- Check whether the source IP falls inside the Login IP Range on the user's profile.
The Login History view is the single most useful diagnostic. Every failed login is recorded with reason code and source IP. The pattern of failures points at the cause.
Quick recovery checklist
- Verify the credentials work from a known-good environment.
- Reset the security token if you are on the legacy username-password flow.
- Reset the lockout or unfreeze the user if necessary.
- Confirm the source IP is allowed.
- Update the integration's secret store and redeploy.
Migrating to OAuth removes most of these failure modes. The investment pays off the first time a password rotation breaks half a dozen integrations and one OAuth-based connector keeps running.
Further reading from Salesforce
- Salesforce Help: Reset Your Security Token
- Salesforce Help: OAuth Flows
- Salesforce Help: Connected Apps
- API Developer Guide: SOAP API Authentication
- Trailhead: Salesforce Connected App Basics
Related dictionary terms
Share this fix
Related Security errors
ENTITY_FAILED_IFLASTMODIFIED_ON_UPDATE_CHECK: record has been modified since you retrieved it
SecurityYou sent an update with an `If-Unmodified-Since` header (or the API equivalent), and the record was modified by someone else after your read…
Insufficient Privileges. You do not have the level of access necessary to perform the operation you requested.
SecurityObject-level access — the user lacks Read/Edit/Create/Delete on the object itself, not on a particular row. Different from "You don't have a…
INSUFFICIENT_ACCESS_OR_READONLY: insufficient access rights on cross-reference id
SecurityThe running user can see the record they're trying to update, but doesn't have edit access to it (or to a record it depends on). The error m…
INVALID_CROSS_REFERENCE_KEY: invalid cross reference id
SecurityYou set a relationship field to an Id that the platform rejects — either the Id doesn't exist, points at the wrong object type, has been del…
invalid_grant: <reason>
SecuritySalesforce rejected the OAuth credentials in your token request. The `error_description` field tells you which part — refresh token revoked/…