exp claim must be within 5 minutes of server time -- clock skew is the #1 cause of JWT auth failures. Connected App creation is disabled by default in Spring '26; use External Client Apps instead. [src2, src4, src8]This card covers all OAuth 2.0 authentication flows supported by Salesforce for API integration as of Spring '26 (API v66.0). It covers the setup, token lifecycle, security considerations, and production gotchas for each flow. This is an authentication-focused card -- it does not cover what you can do after authentication (see related API capability cards for that). All editions that support the REST API support OAuth 2.0, but Connected App/External Client App creation requires admin permissions. [src1, src4]
| Property | Value |
|---|---|
| Vendor | Salesforce |
| System | Salesforce Platform (Spring '26) |
| API Surface | OAuth 2.0 Authentication |
| Current API Version | v66.0 (Spring '26) |
| Editions Covered | Enterprise, Unlimited, Developer, Performance |
| Deployment | Cloud |
| API Docs | OAuth Authorization Flows |
| Status | GA (all supported flows) |
Salesforce supports six active OAuth 2.0 flows (plus one removed). The choice depends on whether you need user context, can manage certificates, and whether a browser is available. [src1, src6]
| OAuth Flow | Protocol | Best For | User Context? | Refresh Token? | Certificate Required? | Security Level |
|---|---|---|---|---|---|---|
| JWT Bearer | OAuth 2.0 | Server-to-server automation (ETL, middleware) | No (integration user) | No (new JWT per request) | Yes (X.509) | Highest |
| Client Credentials | OAuth 2.0 | Server-to-server (simpler setup) | No (execution user) | No (request new token) | No | High |
| Web Server (Auth Code) | OAuth 2.0 | User-context web applications | Yes | Yes | No | High |
| PKCE (Auth Code + PKCE) | OAuth 2.0 | Mobile apps, SPAs, public clients | Yes | Yes | No | High |
| User-Agent (Implicit) | OAuth 2.0 | Legacy SPAs (not recommended) | Yes | No | No | Low |
| Username-Password | OAuth 2.0 | Testing only (breaks with MFA) | Yes (specific user) | No | No | Very Low |
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| JWT assertion validity window | 5 minutes max | JWT Bearer flow | exp claim must be <= 5 min from current time [src2] |
| Token endpoint rate limit | Shared with org API limits | All OAuth flows | Token requests count against 24h API call limit [src1] |
| Max concurrent sessions per user | 5 (default) | All flows | Configurable per profile; oldest session killed when exceeded [src7] |
| Access token size | ~1.5 KB | All flows | Opaque token, not a JWT -- do not parse [src1] |
| Limit Type | Value | Window | Edition Differences |
|---|---|---|---|
| API calls (includes token requests) | 100,000 base | 24h rolling | Enterprise: 100K + (user count x 1,000); Unlimited: 5M; Developer: 15K [src1] |
| Token refresh requests | Counts against API limit | Per request | Each refresh_token exchange = 1 API call [src7] |
| Connected App consent prompts | No hard limit | Per user | Excessive prompts may indicate misconfiguration [src1] |
| Token Type | Default Lifetime | Configurable? | Policy Options |
|---|---|---|---|
| Access token | 2 hours (7,200s) | Yes (session timeout) | Tied to Connected App session policy [src7] |
| Refresh token | Until revoked (default) | Yes (4 policy types) | Until revoked / Expire after N / Expire if unused for N / Immediate expire [src7] |
| JWT assertion | 5 min max | No (hard limit) | Must be < 5 min from iat to exp [src2] |
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| JWT Bearer | Server-to-server, no user interaction, certificate management available | Session timeout (default 2h) | No -- issue new JWT each time | Most secure; recommended for production integrations [src2] |
| Client Credentials | Server-to-server, simpler setup, no certificate infrastructure | Session timeout (default 2h) | No -- request new token | Requires designated execution user in Connected App [src3] |
| Web Server (Auth Code) | User-context operations, interactive web apps | Access: 2h, Refresh: until revoked | Yes | Requires callback URL; most common for web apps [src1] |
| PKCE | Mobile apps, SPAs, public clients (cannot store secret) | Access: 2h, Refresh: until revoked | Yes | Replaces User-Agent flow; code_verifier prevents interception [src6] |
| Username-Password | Testing and development ONLY | Session timeout | No | Incompatible with MFA; never use in production [src1, src6] |
The JWT Bearer flow is the gold standard for server-to-server Salesforce integrations. It uses asymmetric cryptography (private key signs the JWT; public certificate validates it) so no shared secret is transmitted. [src2, src6]
| Claim | Value | Required? | Notes |
|---|---|---|---|
iss | Connected App consumer key (client_id) | Yes | Identifies which Connected App/External Client App [src2] |
sub | Salesforce username of integration user | Yes | All API calls execute in this user's context [src2] |
aud | https://login.salesforce.com or https://test.salesforce.com | Yes | Use test.salesforce.com for sandboxes [src2] |
exp | Expiration (Unix timestamp, seconds) | Yes | Must be <= 5 minutes from now [src2] |
iat | Issued-at (Unix timestamp, seconds) | Optional | Helps with clock skew debugging [src2] |
Signing Algorithm: RS256 (RSA with SHA-256) [src2]
Simpler than JWT Bearer because no certificate is needed -- uses client_id + client_secret instead. Introduced in Spring '23. [src3]
| Parameter | Value | Notes |
|---|---|---|
grant_type | client_credentials | Fixed string [src3] |
client_id | Connected App consumer key | From Connected App settings [src3] |
client_secret | Connected App consumer secret | Must be stored securely; rotate if compromised [src3] |
Execution User: Must designate a "Run As" user in the Connected App OAuth policies. All API calls execute as this user regardless of which client_id/secret is used. [src3]
.eca-meta.xml) instead. Existing Connected Apps continue to work. [src4, src5, src8]invalid_grant. Use NTP sync. Many cloud providers have clock drift on containers. [src2]aud must be https://test.salesforce.com for sandboxes. A common mistake is hardcoding the production endpoint and wondering why sandbox auth fails. [src2]na1.salesforce.com, etc.) were removed. Always use My Domain URL (yourorg.my.salesforce.com). [src8]START -- User needs to authenticate with Salesforce API
|-- Is this server-to-server (no user interaction)?
| |-- YES
| | |-- Can you manage X.509 certificates?
| | | |-- YES --> JWT Bearer flow (recommended, most secure)
| | | | |-- Generate RSA key pair (2048-bit minimum)
| | | | |-- Upload public cert to Connected App/External Client App
| | | | |-- Sign JWT with private key, POST to /services/oauth2/token
| | | | '-- No refresh token; issue new JWT for each session
| | | '-- NO --> Client Credentials flow (simpler)
| | | |-- Create Connected App with client_id + client_secret
| | | |-- Designate execution (Run As) user
| | | |-- POST client_id + client_secret to /services/oauth2/token
| | | '-- No refresh token; request new token when expired
| | '-- Need to impersonate specific users?
| | |-- YES --> JWT Bearer (set sub claim to target username)
| | '-- NO --> Client Credentials (always runs as execution user)
| '-- NO (user interaction available)
| |-- Is the client a web server (can store secrets)?
| | |-- YES --> Web Server (Authorization Code) flow
| | | |-- Redirect user to Salesforce login
| | | |-- Exchange auth code for access + refresh tokens
| | | '-- Use refresh token for long-lived sessions
| | '-- NO (SPA, mobile, public client)
| | '-- PKCE (Authorization Code + PKCE) flow
| | |-- Generate code_verifier and code_challenge
| | |-- Redirect user with code_challenge
| | '-- Exchange code + code_verifier for tokens
'-- NEVER use Username-Password flow in production
'-- NEVER use Device Flow (removed Sept 2025)
| OAuth Flow | Token Endpoint | Grant Type Parameter | Key Credential | Token Response |
|---|---|---|---|---|
| JWT Bearer | POST /services/oauth2/token | urn:ietf:params:oauth:grant-type:jwt-bearer | assertion (signed JWT) | access_token, instance_url, token_type |
| Client Credentials | POST /services/oauth2/token | client_credentials | client_id + client_secret | access_token, instance_url, token_type |
| Web Server (Auth Code) | POST /services/oauth2/token | authorization_code | code + client_id + client_secret | access_token, refresh_token, instance_url |
| PKCE | POST /services/oauth2/token | authorization_code | code + code_verifier + client_id | access_token, refresh_token, instance_url |
| Refresh Token | POST /services/oauth2/token | refresh_token | refresh_token + client_id + client_secret | access_token, instance_url |
| Username-Password | POST /services/oauth2/token | password | username + password + client_id + client_secret | access_token, instance_url |
Since Spring '26 disables Connected App creation by default, the recommended path is to create an External Client App via metadata. [src4, src8]
<!-- force-app/main/default/externalClientApps/MyIntegration.eca-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<ExternalClientApp xmlns="http://soap.sforce.com/2006/04/metadata">
<label>My Integration App</label>
<contactEmail>[email protected]</contactEmail>
<description>Server-to-server integration via JWT Bearer</description>
<oauthConfig>
<callbackUrl>https://login.salesforce.com/services/oauth2/success</callbackUrl>
<scopes>api</scopes>
<scopes>refresh_token</scopes>
<isSecretRequired>false</isSecretRequired>
<certificate>MyCertificateName</certificate>
</oauthConfig>
</ExternalClientApp>
Verify: Deploy with sf project deploy start and confirm the app appears in Setup > App Manager.
Create an RSA key pair and self-signed certificate. For production, use a CA-signed certificate. [src2]
# Input: OpenSSL installed
# Output: private.key (keep secret) + public.crt (upload to Salesforce)
# Generate 2048-bit RSA private key
openssl genrsa -out private.key 2048
# Generate self-signed X.509 certificate (valid 365 days)
openssl req -new -x509 -key private.key -out public.crt -days 365 \
-subj "/CN=SalesforceJWTIntegration/O=YourCompany"
Verify: openssl x509 -in public.crt -text -noout shows certificate details with correct CN.
Construct a JWT, sign it with your private key, and exchange it for an access token. [src2]
# Input: consumer_key (from Connected App), private.key, username
# Output: access_token + instance_url
# Step 1: Create JWT header + payload (in practice, use a JWT library)
# Header: {"alg": "RS256"}
# Payload: {"iss": "<consumer_key>", "sub": "<username>",
# "aud": "https://login.salesforce.com", "exp": <now+300>}
# Step 2: Exchange JWT for access token
curl -X POST https://login.salesforce.com/services/oauth2/token \
-d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
-d "assertion=<signed_jwt_token>"
# Expected response:
# {"access_token":"00D...","scope":"api","instance_url":"https://yourorg.my.salesforce.com","id":"https://login.salesforce.com/id/00Dxx.../005xx...","token_type":"Bearer"}
Verify: Response contains access_token and instance_url. HTTP 200 = success.
Simpler alternative when certificate management is not feasible. [src3]
# Input: client_id + client_secret from Connected App
# Output: access_token + instance_url
curl -X POST https://login.salesforce.com/services/oauth2/token \
-d "grant_type=client_credentials" \
-d "client_id=<consumer_key>" \
-d "client_secret=<consumer_secret>"
# Expected response:
# {"access_token":"00D...","instance_url":"https://yourorg.my.salesforce.com","id":"https://login.salesforce.com/id/00Dxx.../005xx...","token_type":"Bearer"}
Verify: Response contains access_token. Use it in subsequent API calls with Authorization: Bearer <access_token>.
For flows that return refresh tokens, implement automatic refresh before the access token expires. [src1, src7]
# Input: refresh_token + client_id + client_secret
# Output: new access_token
curl -X POST https://login.salesforce.com/services/oauth2/token \
-d "grant_type=refresh_token" \
-d "refresh_token=<refresh_token>" \
-d "client_id=<consumer_key>" \
-d "client_secret=<consumer_secret>"
Verify: Response contains a new access_token. The refresh_token itself may or may not be rotated (depends on Connected App policy).
After obtaining a token, verify it works and check your API quota. [src1]
# Input: access_token + instance_url
# Output: API limits for the org
curl -H "Authorization: Bearer <access_token>" \
https://<instance_url>/services/data/v66.0/limits
# Key fields in response:
# "DailyApiRequests": {"Max": 100000, "Remaining": 99850}
Verify: HTTP 200 with valid JSON. Check DailyApiRequests.Remaining > 0.
# Input: consumer_key, private_key_path, username, login_url
# Output: Salesforce access_token + instance_url
import jwt # PyJWT>=2.8.0
import time
import requests # requests>=2.31.0
def get_salesforce_token_jwt(consumer_key, private_key_path, username,
login_url="https://login.salesforce.com"):
"""Authenticate to Salesforce using OAuth 2.0 JWT Bearer flow."""
with open(private_key_path, "r") as f:
private_key = f.read()
# Build JWT claims
now = int(time.time())
claims = {
"iss": consumer_key, # Connected App consumer key
"sub": username, # Salesforce integration user
"aud": login_url, # login.salesforce.com or test.salesforce.com
"exp": now + 300, # 5 minutes max (Salesforce hard limit)
}
# Sign with RS256
assertion = jwt.encode(claims, private_key, algorithm="RS256")
# Exchange JWT for access token
resp = requests.post(
f"{login_url}/services/oauth2/token",
data={
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
},
)
if resp.status_code != 200:
raise Exception(f"JWT auth failed: {resp.status_code} -- {resp.text}")
data = resp.json()
return data["access_token"], data["instance_url"]
// Input: SALESFORCE_CLIENT_ID, SALESFORCE_CLIENT_SECRET env vars
// Output: { accessToken, instanceUrl }
const https = require("https"); // built-in
const querystring = require("querystring"); // built-in
async function getSalesforceToken() {
const params = querystring.stringify({
grant_type: "client_credentials",
client_id: process.env.SALESFORCE_CLIENT_ID,
client_secret: process.env.SALESFORCE_CLIENT_SECRET,
});
return new Promise((resolve, reject) => {
const req = https.request(
"https://login.salesforce.com/services/oauth2/token",
{ method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" } },
(res) => {
let body = "";
res.on("data", (chunk) => (body += chunk));
res.on("end", () => {
if (res.statusCode !== 200) {
return reject(new Error(`Auth failed: ${res.statusCode} -- ${body}`));
}
const data = JSON.parse(body);
resolve({ accessToken: data.access_token, instanceUrl: data.instance_url });
});
}
);
req.on("error", reject);
req.write(params);
req.end();
});
}
# Input: Connected App credentials
# Output: access_token for API calls
# --- JWT Bearer flow ---
# (Requires a pre-built signed JWT assertion)
curl -s -X POST https://login.salesforce.com/services/oauth2/token \
-d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
-d "assertion=${JWT_ASSERTION}" | jq .
# --- Client Credentials flow ---
curl -s -X POST https://login.salesforce.com/services/oauth2/token \
-d "grant_type=client_credentials" \
-d "client_id=${SF_CLIENT_ID}" \
-d "client_secret=${SF_CLIENT_SECRET}" | jq .
# --- Username-Password flow (testing only, not for production) ---
curl -s -X POST https://login.salesforce.com/services/oauth2/token \
-d "grant_type=password" \
-d "client_id=${SF_CLIENT_ID}" \
-d "client_secret=${SF_CLIENT_SECRET}" \
-d "username=${SF_USERNAME}" \
-d "password=${SF_PASSWORD}${SF_SECURITY_TOKEN}" | jq .
# Expected success response shape:
# {
# "access_token": "00Dxx...",
# "instance_url": "https://yourorg.my.salesforce.com",
# "id": "https://login.salesforce.com/id/00Dxx.../005xx...",
# "token_type": "Bearer",
# "issued_at": "1709251200000"
# }
| Response Field | Type | Present In | Notes |
|---|---|---|---|
access_token | String | All flows | Opaque token -- use as-is in Authorization header [src1] |
instance_url | String | All flows | Base URL for all subsequent API calls -- never hardcode [src1] |
refresh_token | String | Web Server, PKCE only | Use to obtain new access_token without re-auth [src1] |
token_type | String | All flows | Always "Bearer" [src1] |
id | String (URL) | All flows | Identity URL -- GET to retrieve user info [src1] |
issued_at | String (epoch ms) | All flows | Milliseconds since Unix epoch [src1] |
scope | String | JWT Bearer, Client Credentials | Space-separated list of granted scopes [src2, src3] |
signature | String | All flows | HMAC-SHA256 of id + issued_at using consumer secret [src1] |
issued_at is a string of epoch milliseconds (not seconds) -- divide by 1000 for Unix timestamp comparison. [src1]instance_url may change between token requests if the org is migrated to a different Salesforce instance. Always use the instance_url from the most recent token response. [src1]access_token is opaque -- do not attempt to decode it as a JWT. Its format and length can change without notice. [src1]refresh_token from Web Server flow may be a new token on each refresh (token rotation) depending on Connected App policy. Always store the latest refresh token. [src7]| Error | Meaning | Cause | Resolution |
|---|---|---|---|
invalid_grant | JWT assertion invalid or expired | Clock skew >5 min, wrong aud, revoked certificate, or user not pre-authorized | Sync server clock via NTP; verify aud matches environment; check Connected App pre-authorization [src2] |
invalid_client_id | Consumer key not found | Wrong client_id or Connected App not deployed to this org | Verify consumer key in Setup > App Manager [src1] |
invalid_client | Client authentication failed | Wrong client_secret, or secret was rotated | Re-copy consumer secret from Connected App settings [src3] |
unsupported_grant_type | Grant type not enabled | Client Credentials not checked, or flow not supported for this app | Enable the specific flow in Connected App OAuth settings [src3] |
inactive_user | Integration user is inactive or frozen | User account deactivated or locked out | Reactivate user in Setup > Users [src2] |
inactive_org | Org is locked, suspended, or restricted | Billing, compliance, or admin lock | Contact Salesforce support [src1] |
INVALID_LOGIN | Username or password incorrect | Wrong credentials in Username-Password flow | Verify username + password + security token [src1] |
redirect_uri_mismatch | Callback URL mismatch | Web Server flow redirect_uri doesn't match Connected App | Update callback URL in Connected App settings [src1] |
invalid_grant. Fix: configure NTP sync in container images; use cloud provider's time sync service (AWS: chrony, GCP: metadata server, Azure: VMICTimeProvider). [src2]implement a health check that refreshes the token at least once within the inactivity window; alert on consecutive 401 responses. [src7]use JWT Bearer flow (certificate-based) for production; when rotating secrets, update all clients before regenerating. [src3]always use the instance_url from the token response; never hardcode it. [src8]implement proactive token refresh at 75% of expected TTL; handle 401 responses with automatic re-authentication. [src7]invalid_grant. Fix: set calendar reminders for certificate renewal; implement certificate monitoring; use short-lived certificates (90 days) with automated rotation. [src2, src4]# BAD -- instance URL can change if org is migrated
BASE_URL = "https://na1.salesforce.com" # Legacy hostname, removed in Spring '26
resp = requests.get(f"{BASE_URL}/services/data/v66.0/query?q=...", headers=headers)
# GOOD -- dynamic instance URL from authentication response
access_token, instance_url = get_salesforce_token_jwt(key, cert, user)
resp = requests.get(f"{instance_url}/services/data/v66.0/query?q=...",
headers={"Authorization": f"Bearer {access_token}"})
# BAD -- assumes 2-hour token lifetime; admin can change session timeout
TOKEN_CACHE = {"token": None, "expires": 0}
def get_token():
if time.time() < TOKEN_CACHE["expires"]:
return TOKEN_CACHE["token"]
token = authenticate()
TOKEN_CACHE["token"] = token
TOKEN_CACHE["expires"] = time.time() + 7200 # hardcoded 2 hours
return token
# GOOD -- defensive re-authentication on token expiry
def api_call(instance_url, token, method, endpoint, **kwargs):
resp = requests.request(method, f"{instance_url}{endpoint}",
headers={"Authorization": f"Bearer {token}"}, **kwargs)
if resp.status_code == 401: # token expired or revoked
token, instance_url = get_salesforce_token_jwt(key, cert, user)
resp = requests.request(method, f"{instance_url}{endpoint}",
headers={"Authorization": f"Bearer {token}"}, **kwargs)
resp.raise_for_status()
return resp.json()
# BAD -- credentials in plaintext, breaks with MFA, security risk
resp = requests.post("https://login.salesforce.com/services/oauth2/token", data={
"grant_type": "password",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"username": "[email protected]",
"password": "P@ssw0rd123SecurityToken456" # credentials in code
})
# GOOD -- no credentials transmitted; certificate-based authentication
import jwt
claims = {"iss": CLIENT_ID, "sub": "[email protected]",
"aud": "https://login.salesforce.com", "exp": int(time.time()) + 300}
assertion = jwt.encode(claims, private_key, algorithm="RS256")
resp = requests.post("https://login.salesforce.com/services/oauth2/token", data={
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion
})
https://test.salesforce.com/services/oauth2/token. Using the production endpoint returns invalid_grant. Also set the JWT aud claim to https://test.salesforce.com. [src2]password + securityToken). If the user's IP is in a trusted range, the token is not required -- but this varies and breaks when the user changes location. [src1]user hasn't approved this consumer. [src2]error and error_description fields for debugging. [src1, src2]id URL in the token response: The identity URL (id field) can be GET-requested to retrieve user details, org info, and verify the integration user's identity. Useful for debugging. [src1]# Test JWT Bearer authentication
curl -v -X POST https://login.salesforce.com/services/oauth2/token \
-d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
-d "assertion=${JWT_ASSERTION}"
# Test Client Credentials authentication
curl -v -X POST https://login.salesforce.com/services/oauth2/token \
-d "grant_type=client_credentials" \
-d "client_id=${SF_CLIENT_ID}" \
-d "client_secret=${SF_CLIENT_SECRET}"
# Check token validity and user identity
curl -H "Authorization: Bearer ${ACCESS_TOKEN}" \
https://${INSTANCE_URL}/services/oauth2/userinfo
# Check remaining API limits (confirms token works)
curl -H "Authorization: Bearer ${ACCESS_TOKEN}" \
https://${INSTANCE_URL}/services/data/v66.0/limits
# Introspect token (check expiration)
curl -X POST https://login.salesforce.com/services/oauth2/introspect \
-d "token=${ACCESS_TOKEN}" \
-d "client_id=${SF_CLIENT_ID}" \
-d "client_secret=${SF_CLIENT_SECRET}" \
-d "token_type_hint=access_token"
# Revoke a token (useful for testing token expiry handling)
curl -X POST https://login.salesforce.com/services/oauth2/revoke \
-d "token=${ACCESS_TOKEN}"
# Verify certificate (check expiration date)
openssl x509 -in public.crt -enddate -noout
| API Version | Release | Status | Key Auth Changes | Notes |
|---|---|---|---|---|
| v66.0 | Spring '26 (Feb 2026) | Current | Connected App creation disabled by default; External Client Apps recommended | Legacy hostname redirects removed [src8] |
| v63.0 | Spring '25 (Feb 2025) | Supported | Uninstalled Connected Apps blocked; Device Flow removed (Sept 2025) | "Use Any API Client" permission introduced [src5] |
| v58.0 | Summer '23 (Jun 2023) | Supported | Client Credentials "API Only" user restriction removed | Any user can be execution user [src3] |
| v57.0 | Spring '23 (Feb 2023) | Supported | Client Credentials flow introduced (GA) | New server-to-server option [src3] |
| v51.0 | Spring '21 (Feb 2021) | Supported | Token exchange flow introduced | For IoT/asset token scenarios [src1] |
| v29.0 | Winter '14 (Oct 2013) | EOL | JWT Bearer flow introduced | Original server-to-server flow [src2] |
| v21.0 | Spring '11 | EOL | OAuth 2.0 support introduced | Web Server + User-Agent flows [src1] |
Salesforce supports API versions for a minimum of 3 years. API versions 21.0 through 30.0 were retired in June 2025. OAuth flows themselves are not versioned separately -- they depend on the Connected App/External Client App configuration, not the API version used in subsequent calls. However, the token endpoint URL and flow availability can change with major releases (e.g., Device Flow removal in Sept 2025, Connected App creation restriction in Spring '26). Always monitor Salesforce Release Notes before each major release. [src5, src8]
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Server-to-server automation (ETL, middleware, batch jobs) | End-user SSO/login to Salesforce UI | SAML 2.0 or OpenID Connect |
| API calls must execute without user interaction | Mobile app needs user authentication | PKCE (Authorization Code + PKCE) |
| Integration needs to impersonate a specific user | Client cannot manage certificates and needs simplicity | Client Credentials flow |
| Building a web application with user-initiated Salesforce actions | Org has MFA enforced and you need quick scripting | JWT Bearer or Client Credentials |
| Need long-lived access to Salesforce data | One-time data export | Salesforce Data Export (Setup > Data Export) |
test.salesforce.com vs login.salesforce.com). Parameterize the login URL in your integration configuration. [src2]sub values). [src3]