Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
All errors
Security

invalid_grant: invalid_grant - challenge required / TWO_FACTOR_REQUIRED

Salesforce required Multi-Factor Authentication for the login but the integration didn't supply it. Most common with Username-Password OAuth flow against a profile that requires MFA. The fix is to switch the integration to JWT bearer flow (which is MFA-exempt) or to give the integration user the "API Only User" permission set.

Also seen asTWO_FACTOR_REQUIRED·challenge required·MFA required·Multi-Factor Authentication

A nightly data-sync job that's been running for eighteen months suddenly starts returning OAuth errors. The token-exchange call to Salesforce returns {"error":"invalid_grant","error_description":"challenge required"}. The integration user's password is unchanged. The connected app's client secret is unchanged. Something on the Salesforce side changed: the org's security policy now requires multi-factor authentication for API logins, and the integration was never set up to handle the challenge.

What the platform is checking

Salesforce supports requiring multi-factor authentication on a per-user basis or via an org-wide policy. When MFA is required, every authentication attempt (UI login, API login, OAuth token exchange) must include proof of a second factor. The factor can be a TOTP code, a hardware key, a push notification through the Salesforce Authenticator mobile app, or another supported method.

For UI logins, MFA is transparent: the user types their password, the platform challenges them, they tap "approve" on their phone or type the code, login completes. For API and OAuth logins, the same challenge exists but the flow is different. The legacy username/password OAuth grant type can't natively prompt for MFA. The token exchange returns invalid_grant with a description like challenge required or TWO_FACTOR_REQUIRED instead.

The check is intentional. Username/password OAuth (also called Resource Owner Password Credentials) was always a security compromise; it requires storing the user's actual password in the integration. Salesforce phased in restrictions over several releases, and orgs that opted into stricter security found their integrations breaking.

The fix is not to disable MFA. The fix is to migrate the integration away from username/password OAuth toward a flow that handles MFA correctly or doesn't need it.

The broken example

A typical integration using the username/password OAuth grant:

import requests

def get_access_token():
    response = requests.post(
        'https://login.salesforce.com/services/oauth2/token',
        data={
            'grant_type': 'password',
            'client_id': CLIENT_ID,
            'client_secret': CLIENT_SECRET,
            'username': INTEGRATION_USER,
            'password': INTEGRATION_PASSWORD + INTEGRATION_SECURITY_TOKEN
        }
    )
    return response.json()['access_token']

This worked for years. The day the org enforces MFA for API users, the response becomes:

{
  "error": "invalid_grant",
  "error_description": "challenge required"
}

The integration has no way to handle the challenge. The grant type doesn't support it; the password+token combination isn't enough anymore.

A second shape: a session-id-based integration using SOAP login. The SOAP login also fails with INVALID_LOGIN or a similar code once MFA is in force.

A third shape: a desktop tool (Workbench, Data Loader Command Line) that uses username/password under the hood. The tool fails the same way; the user sees an error and assumes their password is wrong.

The fix, three paths

Migrate to JWT Bearer Token flow. The JWT flow uses a server-side certificate to prove the integration's identity. No user password is involved. The integration signs a JWT with its private key; Salesforce verifies the signature against the public certificate uploaded to the Connected App. MFA doesn't apply because no human credentials are in play.

import jwt
import time
import requests

def get_access_token():
    now = int(time.time())
    payload = {
        'iss': CLIENT_ID,
        'sub': INTEGRATION_USER,
        'aud': 'https://login.salesforce.com',
        'exp': now + 300
    }
    assertion = jwt.encode(payload, PRIVATE_KEY, algorithm='RS256')
    response = requests.post(
        'https://login.salesforce.com/services/oauth2/token',
        data={
            'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
            'assertion': assertion
        }
    )
    return response.json()['access_token']

This is the modern standard for server-to-server integrations. The Connected App must be configured with the public certificate and the integration user must be pre-authorized.

Migrate to Client Credentials flow. For integrations that don't act on behalf of a specific user, the Client Credentials flow is a clean fit. The integration uses the Connected App's consumer key and consumer secret to obtain a token. The flow is simpler than JWT and doesn't need a certificate, but the Connected App must be configured with a Run As user and Client Credentials enabled.

def get_access_token():
    response = requests.post(
        'https://login.salesforce.com/services/oauth2/token',
        data={
            'grant_type': 'client_credentials',
            'client_id': CLIENT_ID,
            'client_secret': CLIENT_SECRET
        }
    )
    return response.json()['access_token']

The Connected App's Run As user is the identity the token represents. MFA on that user is bypassed because the OAuth flow doesn't authenticate as a human; it authenticates as the app.

Exempt the integration user from MFA. As a last-resort interim solution, an admin can grant the integration user the "Multi-Factor Authentication for User Interface Logins" exemption permission. This exempts the user from MFA for API logins. Salesforce strongly recommends migrating to JWT or Client Credentials instead; the exemption is a temporary bridge, not a long-term answer.

The exemption is on a user-by-user basis. Setup, Permission Sets, create a permission set with "Multi-Factor Authentication for User Interface Logins" turned off. Assign to the integration user. The user can now use password OAuth without MFA challenges.

The fixed example

A JWT flow implementation:

import jwt
import time
import requests

class SalesforceJWTClient:
    def __init__(self, client_id, username, private_key_path, login_url='https://login.salesforce.com'):
        self.client_id = client_id
        self.username = username
        self.login_url = login_url
        with open(private_key_path, 'r') as f:
            self.private_key = f.read()
        self.access_token = None
        self.instance_url = None
        self.expires_at = 0

    def authenticate(self):
        now = int(time.time())
        payload = {
            'iss': self.client_id,
            'sub': self.username,
            'aud': self.login_url,
            'exp': now + 300
        }
        assertion = jwt.encode(payload, self.private_key, algorithm='RS256')
        response = requests.post(
            f'{self.login_url}/services/oauth2/token',
            data={
                'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
                'assertion': assertion
            }
        )
        response.raise_for_status()
        payload = response.json()
        self.access_token = payload['access_token']
        self.instance_url = payload['instance_url']
        self.expires_at = time.time() + 3600

    def get_valid_token(self):
        if time.time() > self.expires_at - 60:
            self.authenticate()
        return self.access_token

The client obtains a token at startup and refreshes proactively. No user password is in the configuration. MFA on the integration user doesn't matter because the JWT proves identity without password authentication.

Connected App setup for JWT

A Connected App needs specific configuration to support JWT:

  1. Setup, App Manager, New Connected App.
  2. API (Enable OAuth Settings): yes.
  3. Callback URL: any placeholder; JWT doesn't use it but the field is required.
  4. Selected OAuth Scopes: typically api, refresh_token, offline_access.
  5. Use digital signatures: yes. Upload the public certificate (X.509 PEM format) that pairs with your integration's private key.

After saving:

  1. Manage, Edit Policies.
  2. Permitted Users: Admin approved users are pre-authorized.
  3. Assign the Connected App to a Permission Set or Profile that includes the integration user.

The integration user is now pre-authorized for the Connected App. The JWT flow can issue tokens for that user without an interactive consent step.

Generating the certificate

For dev environments, a self-signed cert works:

openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout server.key -out server.crt -subj "/C=US/ST=CA/L=SF/O=Acme/CN=salesforce-integration"

The .crt file goes to the Connected App. The .key file stays with the integration (treated as a secret, never committed). For production, prefer a cert issued by a known CA; the validation surface is the same but the auditing story is cleaner.

Rotation matters. The cert in the Connected App has an expiration date. When it nears expiration, generate a new keypair, upload the new cert to the Connected App, deploy the new key to the integration, then remove the old cert. Plan rotations far in advance; an expired cert means the integration is dark.

Migrating without downtime

Existing integrations using username/password OAuth can be migrated incrementally:

  1. Configure the Connected App for JWT in addition to the existing username/password setup.
  2. Deploy the new JWT-based integration code to a feature flag.
  3. Test against staging or sandbox.
  4. Enable the JWT path for one integration at a time.
  5. After verifying, remove the username/password fallback.

The two flows can coexist in the same Connected App during the migration. Once every integration is on JWT, revoke the username/password tokens entirely.

Edge case: Salesforce-managed Connected Apps

Some integrations use a Connected App that ships with a managed package (Data Loader, Workbench, third-party AppExchange tools). You can't always reconfigure the Connected App. The fix in those cases is either to update to a newer version of the tool that supports JWT or, less ideally, to grant the integration user the MFA exemption.

For long-term reliability, prefer tools that support JWT natively. Data Loader, for example, can use JWT-based auth in recent versions.

Edge case: scratch orgs and sandboxes

Sandboxes and scratch orgs inherit MFA policy from the production org during refresh. An integration that worked in a sandbox before refresh might break after refresh because the new sandbox has stricter security than the old one.

Sandbox refreshes are also a good time to verify that integration users are pre-authorized for Connected Apps. The Permission Set assignments survive the refresh, but if you create the Connected App fresh in each sandbox, the assignments need re-creation.

A subtle case: Permitted Users vs Authenticate As

A Connected App with "Admin approved users are pre-authorized" requires the integration user to be explicitly assigned via Profile or Permission Set. The assignment grants the right to obtain tokens for this Connected App; it doesn't grant the user any other capabilities.

The assignment is a small step that gets forgotten frequently during initial setup. The result is a Connected App that exists, has the right scopes, has a valid certificate, and still returns invalid_grant: user hasn't approved this consumer. The fix is to assign the Connected App via Permission Set Group or Profile. Document this step in the integration's runbook so the next deploy doesn't repeat the omission.

Setup → Permission Sets → New
  Name: Integration_App_Access
  Connected App Access: check the Connected App
  Apex Class Access: any classes the integration needs
Save and assign to the integration user.

The Permission Set isolates the access grant from the user's broader permission profile, which makes the grant easy to revoke if the integration is decommissioned.

A subtle case: certificate rotation timing

JWT integrations depend on a private key on the integration side and a public certificate on the Salesforce side. The pair must match. When the certificate is rotated (annual hygiene, post-incident, on cert expiry), both sides must update in lockstep.

The pattern that works without downtime is to upload the new certificate to the Connected App while the old one is still active. Salesforce supports multiple certificates per Connected App during a rotation window. The integration switches to the new private key, validates that authentication still works, then the old certificate is removed.

The window where both certificates are accepted is the safety margin. If the new key fails for any reason, the integration can roll back to the old one without an outage. Plan rotations during low-traffic windows just in case.

Defensive habits

Audit every integration for grant type. A simple spreadsheet listing each integration, the Salesforce user it acts as, and the OAuth grant type it uses. Tag username/password entries as migration candidates. The spreadsheet itself is the deliverable for the migration project; quantifying scope is the first step.

Plan the migration before MFA enforcement. Salesforce announces MFA mandates several releases ahead. Once an org commits to MFA, every username/password integration is on borrowed time. Migrating proactively avoids a forced sprint when enforcement begins.

Test JWT in a sandbox before production. The certificate, the user pre-authorization, the Connected App scopes can all be verified in a sandbox without putting production traffic at risk. Use a sandbox that's been refreshed recently so the org configuration matches production.

Never share integration user credentials. Each integration should have its own user (or its own Run As user, for Client Credentials). When the integration retires, the user retires with it. Shared service accounts are an audit nightmare; separation makes ownership and decommissioning straightforward.

Quick recovery checklist

When challenge required or invalid_grant fires:

  1. Identify the grant type. Username/password is the most common cause.
  2. Check the integration user. Is MFA required for them?
  3. If MFA is required and you can't migrate immediately, grant the user the MFA-API exemption as a temporary bridge.
  4. Plan the migration to JWT or Client Credentials.
  5. Test in sandbox; deploy to production; retire the username/password setup.

Most incidents are minor once the migration plan is in motion. The painful ones are organizations that have dozens of legacy integrations all using username/password and need to migrate in a single quarter.

Further reading from Salesforce

Related dictionary terms

Share this fix

Share on LinkedInShare on X

Related Security errors