invalid_grant: <reason>
Salesforce rejected the OAuth credentials in your token request. The `error_description` field tells you which part — refresh token revoked/expired, client credentials wrong, redirect URI mismatch, MFA required. Each has a different fix.
Also seen asinvalid_grant·invalid_grant: invalid·invalid_grant: expired·OAuth invalid_grant
A marketing automation tool that's been syncing leads to Salesforce for six months stops working over the weekend. The vendor's diagnostic UI shows invalid_grant: authentication failure on every retry. The Salesforce admin says nothing changed. The vendor's engineer points the finger at Salesforce. The integration team is in the middle, trying to figure out what's actually broken.
What the platform is checking
The OAuth invalid_grant response is Salesforce's way of saying "the credentials you sent are not acceptable, and I'm not going to tell you exactly why". The vague response is intentional. Detailed error messages for failed authentication attempts would help attackers distinguish "user doesn't exist" from "user exists but wrong password", which is a privacy and security leak. So invalid_grant covers a broad range of underlying causes.
The error_description field sometimes narrows the diagnosis. Common descriptions:
authentication failure: the username/password combination is wrong.IP restricted: the source IP isn't in the user's login range.inactive user: the user account is deactivated or frozen.password expired: the user's password expired and must be reset before login.challenge required: MFA is required but the flow doesn't support it.expired access/refresh token: the token used in a refresh-token flow is no longer valid.user hasn't approved this consumer: the user must explicitly authorize the Connected App.invalid grant type: the Connected App doesn't allow the OAuth flow being attempted.
Each description points at a different fix. The diagnosis starts with reading the description carefully and identifying which of the above matches.
The broken examples
A username/password OAuth call with an expired password:
response = requests.post(
'https://login.salesforce.com/services/oauth2/token',
data={
'grant_type': 'password',
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
'username': 'integration@acme.com',
'password': PASSWORD + SECURITY_TOKEN
}
)
# Returns: {"error":"invalid_grant","error_description":"password expired"}
The integration user's password expired. The user setting "Password Never Expires" wasn't checked. The next password expiration cycle fired and the user is locked until an admin resets the password.
A refresh-token call after a security event:
response = requests.post(
'https://login.salesforce.com/services/oauth2/token',
data={
'grant_type': 'refresh_token',
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
'refresh_token': STORED_REFRESH_TOKEN
}
)
# Returns: {"error":"invalid_grant","error_description":"expired access/refresh token"}
The user changed their password, an admin revoked the Connected App, or the user's session was forcibly logged out. All three events invalidate refresh tokens. The integration needs to re-authenticate from scratch (interactive consent flow or JWT) to obtain new tokens.
A JWT call with a Connected App not pre-authorized for the user:
response = requests.post(
'https://login.salesforce.com/services/oauth2/token',
data={
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': signed_jwt
}
)
# Returns: {"error":"invalid_grant","error_description":"user hasn't approved this consumer"}
The Connected App requires admin pre-approval. The integration user isn't on the approved list. The fix is for an admin to assign the Connected App to a Permission Set or Profile that includes the integration user.
The fix, three paths (and a flowchart)
The fix depends on which error_description you see. A practical flow:
If "authentication failure": the credentials are wrong. Check the username, password, security token. Reset if necessary. Confirm the user is the right one.
If "IP restricted": the source IP isn't allowed. Either add the IP to the user's login range (Setup, Profiles, Login IP Ranges) or remove the IP restriction. For cloud integrations whose source IP changes, an MFA-exempt user with no IP restriction plus a Connected App scope restriction often makes more sense than an IP allow-list.
If "inactive user": the user is deactivated or frozen. Reactivate the user. Verify nobody intended to deactivate them; if the deactivation was intentional, the integration needs to be re-pointed at a different user.
If "password expired": an admin must reset the password and provide the new value to the integration. Set "Password Never Expires" on the user's profile to prevent future occurrences (or, better, migrate the integration away from password-based auth).
If "challenge required": MFA is now required for this user. Migrate to JWT Bearer or Client Credentials flow.
If "expired access/refresh token": the integration's tokens were invalidated. Re-authenticate from scratch. For JWT integrations, the certificate-based authentication can recover automatically; for refresh-token flows, an interactive consent step is needed.
If "user hasn't approved this consumer": the Connected App isn't authorized for this user. Either grant admin pre-approval via a Permission Set assignment or have the user complete the interactive consent flow once.
If "invalid grant type": the Connected App's OAuth Policies don't include the flow being attempted. Setup, App Manager, edit the Connected App, check Selected OAuth Scopes and Permitted Users settings. Make sure the grant type in use is enabled.
The fixed example
A retry loop that handles the common invalid_grant causes:
import time
import requests
class SalesforceClient:
def __init__(self, client_id, client_secret, username, password, security_token):
self.client_id = client_id
self.client_secret = client_secret
self.username = username
self.password = password
self.security_token = security_token
self.access_token = None
def authenticate(self):
response = requests.post(
'https://login.salesforce.com/services/oauth2/token',
data={
'grant_type': 'password',
'client_id': self.client_id,
'client_secret': self.client_secret,
'username': self.username,
'password': self.password + self.security_token
}
)
body = response.json()
if 'access_token' in body:
self.access_token = body['access_token']
return
error = body.get('error')
description = body.get('error_description', '')
if 'password expired' in description:
raise PasswordExpired('Integration user password expired. Admin must reset.')
elif 'IP restricted' in description:
raise IPRestricted('Source IP not in login range. Update login range or use VPN.')
elif 'inactive user' in description:
raise UserInactive('Integration user is deactivated.')
elif 'challenge required' in description:
raise MFARequired('User now requires MFA. Migrate to JWT or Client Credentials flow.')
else:
raise AuthenticationFailed(f'OAuth failed: {error} - {description}')
The exception types make incident response faster. Each maps to a specific runbook step. A PasswordExpired exception triggers an alert to the Salesforce admin; an MFARequired exception triggers a migration ticket.
The Connected App settings to verify
When invalid_grant fires repeatedly, walk through the Connected App configuration:
- Setup, App Manager, find the Connected App, click Manage.
- Check OAuth Policies. Permitted Users: is it "All users may self-authorize" or "Admin approved users are pre-authorized"? Pre-authorization requires explicit permission set or profile assignment to the integration user.
- Check Selected OAuth Scopes. The integration needs at least
api. For long-running integrations,refresh_tokenandoffline_accessenable refresh-token flows. - Check IP Relaxation. For server integrations whose IP changes, set to "Relax IP restrictions". For locked-down integrations, leave at "Enforce IP restrictions".
- Check whether the Connected App's certificate has expired (if using JWT). An expired certificate produces a JWT verification failure that surfaces as
invalid_grant.
Going through each setting takes about ten minutes. Most invalid_grant mysteries resolve at one of these checkpoints.
Connected App scopes and the dance with permissions
The OAuth scopes selected in the Connected App must match what the integration actually does. A Connected App with only the chatter_api scope can't make Account queries; the API call returns 403 even after successful authentication.
Common scope combinations:
- Server-to-server data integration:
api,refresh_token,offline_access. - Mobile app:
api,refresh_token,web,id. - Chatter-only integration:
chatter_api,id. - Full sandbox sync:
full,refresh_token.
The full scope grants access to everything the user can see. Use sparingly; smaller scopes are safer.
Login range and IP restrictions
A user's login range is configured on their Profile (Setup, Profiles, the user's profile, Login IP Ranges section). Each entry specifies a range of allowed source IPs. A login attempt from outside any range returns invalid_grant: IP restricted.
For integrations running on rotating-IP infrastructure (AWS Lambda, Azure Functions), the source IP can change with every invocation. Options:
- Allowlist the full IP range published by the cloud provider. AWS publishes a JSON list of all Lambda IP ranges; you can import these into Salesforce as a Login IP Range.
- Route the integration through a fixed-IP proxy (NAT gateway, VPN endpoint).
- Remove IP restrictions for the integration user and rely on other security layers (MFA via JWT, OAuth scope limits, monitoring).
The choice depends on your security posture. Allowlisting hundreds of IPs is operationally annoying but auditable; removing the restriction is simpler but requires compensating controls.
Edge case: refresh tokens that go bad silently
Refresh tokens are invalidated by several events that the integration can't directly observe:
- User changes their password.
- Admin revokes the Connected App's OAuth tokens (Setup, Connected Apps OAuth Usage).
- The org rolls a major security event (mass logout, MFA enforcement rollout).
- The refresh token expires per the Connected App's refresh-token policy ("Refresh token is valid until revoked" vs "Refresh token is valid for a number of hours").
The integration discovers the failure on the next token-refresh attempt. The fix is to re-authenticate from scratch (interactive consent or JWT-based re-auth) and store the new tokens.
Edge case: sandbox vs production endpoints
A common misconfiguration: the integration is pointed at login.salesforce.com when it should be test.salesforce.com (or vice versa). The wrong endpoint accepts the request but rejects the credentials because they're valid for a different org.
Verify the endpoint matches the org. Production: login.salesforce.com. Sandbox: test.salesforce.com. My Domain orgs can also use <mydomain>.my.salesforce.com for both.
A subtle case: Connected App propagation delays
After creating or modifying a Connected App, Salesforce takes up to ten minutes to propagate the change. An integration that tries to authenticate immediately after the Connected App is saved can see invalid_grant: client identifier invalid. The fix is patience: wait ten minutes before testing. CI pipelines that create Connected Apps for ephemeral scratch orgs should build in the delay or use exponential-backoff retries.
A subtle case: token introspection failures
A token that worked an hour ago can stop working if the underlying user changes profile or permission set assignments. The error surfaces as invalid_grant on the next refresh attempt. For JWT integrations the re-issue is automatic; for refresh-token flows, an interactive consent step is needed unless the user's permissions are restored. Freezing the user's permission set assignments prevents this class of surprise; the trade-off is that legitimate permission updates require coordination.
Defensive habits
Use a dedicated integration user, never a real person's account. The user's password lifecycle is then under your team's control, not subject to a human leaving the company or rotating their personal password. The integration user can also be named clearly (integration.marketing@acme.com) so audit logs make sense.
Set "Password Never Expires" on integration users. Password rotation for service accounts is a security theater; the password is in a vault, not on a sticky note. Expiration just creates downtime. The exception is if you're rotating credentials as part of a real security event (a leaked secret, a departed contractor), and those rotations should be deliberate, not calendar-driven.
Monitor OAuth failures. Salesforce's Login History (Setup, Login History) records every authentication attempt with status. A spike of invalid_grant failures from a single integration is an early warning. Pair the monitoring with an alert that pages the on-call when failures exceed a threshold.
Document each integration: the user it acts as, the Connected App it uses, the OAuth scopes, the runbook for invalid_grant. The runbook should map error descriptions to specific actions. The first time someone reads the runbook is usually at 2 AM during an outage; the document should be readable in that state.
Quick recovery checklist
When invalid_grant fires:
- Read the
error_description. Identify which case matches. - Apply the case-specific fix (reset password, add IP, reactivate user, migrate to JWT).
- Re-try authentication.
- If the error persists with a different description, repeat the diagnosis with the new description.
- After resolution, document the cause and update the runbook.
Most incidents resolve in 15-30 minutes once the description is read carefully. The longer incidents tend to involve coordinating with the vendor's support team to share enough log detail to identify which case applies.
Further reading from Salesforce
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: invalid_grant - challenge required / TWO_FACTOR_REQUIRED
SecuritySalesforce required Multi-Factor Authentication for the login but the integration didn't supply it. Most common with Username-Password OAuth…