Secure, auditable, and maintainable authentication is the foundation of every reliable Salesforce integration. For data migration, where millions of records may transit through a single integration user credential over hours or days, the authentication architecture is not an afterthought; it is a first-class engineering concern.

In Part 1 of this series, we covered org readiness API limits, metadata inventory, and storage projections. This article covers the three authentication patterns that matter most for Salesforce data migration: OAuth 2.0 Client Credentials Flow, JWT Bearer Token Flow, and Named Credentials, and how to set up your Connected App correctly before any of them work the way you need.

Table of Contents

Connected App Configuration

A Salesforce Connected App is the OAuth 2.0 client registration that authorises an external application to interact with your org. For a data migration integration, the Connected App should be purpose-built for the migration workload and retired or deactivated after completion.

Connected App SettingRecommended ValueRationale
OAuth Scopesapi, bulk_api, refresh_tokenMinimum scopes for Bulk API 2.0 and REST access
IP RelaxationRelax IP restrictions (migration servers)Allows migration server IPs without whitelist churn
Refresh Token PolicyExpire refresh token after 90 daysLimits exposure without forcing re-auth during migration
Require Secret for Web ServerEnabledPrevents unauthorised client_credentials flows
Enable Client Credentials FlowEnabled (for server-to-server)Removes user interaction from automated migration jobs
Permitted UsersAdmin approved users are pre-authorisedLimits OAuth to the migration integration user only

OAuth 2.0 Flows for Server-to-Server Migration

Client Credentials Flow (Recommended for Automation)

The OAuth 2.0 Client Credentials Flow is the recommended authentication pattern for fully automated, server-to-server migration jobs. Unlike the Web Server Flow (which requires a browser redirect and user interaction) or the JWT Bearer Token Flow (which requires certificate management), the Client Credentials Flow is operationally simpler: the external application exchanges its client_id and client_secret for an access token directly, with no user session involved.

				
					OAuth 2.0 Client Credentials Flow: Salesforce Token Exchange

  +--------------------------+          +----------------------------------+
  |  External Migration App  |          |       Salesforce Platform        |
  +-----------+--------------+          +-------------+--------------------+
              |                                       |
              |  POST /services/oauth2/token          |
              |  grant_type=client_credentials        |
              |  client_id=        |
              |  client_secret= |
              |-------------------------------------->|
              |                                       |
              |          200 OK                       |
              |  {                                    |
              |    access_token: 'XXXXXX',            |
              |    instance_url: 'https://org.sf.com',|
              |    token_type:   'Bearer',            |
              |    scope:        'api bulk_api'       |
              |  }                                    |
              |<--------------------------------------|
              |                                       |
              |  Authorization: Bearer  |
              |  (All subsequent API calls)           |
              |-------------------------------------->|


				
			
				
					// Node.js: OAuth 2.0 Client Credentials Token Acquisition
const axios = require('axios');
const qs    = require('querystring');

async function getAccessToken(config) {
  const response = await axios.post(
    `${config.sfLoginUrl}/services/oauth2/token`,
    qs.stringify({
      grant_type   : 'client_credentials',
      client_id    : config.clientId,
      client_secret: config.clientSecret,
    }),
    { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
  );
  return {
    accessToken : response.data.access_token,
    instanceUrl : response.data.instance_url,
    expiresAt   : Date.now() + 3600 * 1000, // 1-hour TTL
  };
}

// Singleton token cache: avoids re-authentication on every API call
let tokenCache = null;

async function getValidToken(config) {
  if (!tokenCache || Date.now() >= tokenCache.expiresAt - 60000) {
    tokenCache = await getAccessToken(config);
    console.log(`[Auth] Token refreshed at ${new Date().toISOString()}`);
  }
  return tokenCache;
}

				
			

JWT Bearer Token Flow (Certificate-Based)

For organisations with strict secret management policies that prohibit long-lived client secrets, the JWT Bearer Token Flow provides a certificate-based alternative. The external application signs a JWT assertion using an RSA private key, and Salesforce validates the assertion against the public certificate registered in the Connected App. This approach integrates naturally with enterprise secret management solutions such as HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault.

				
					# Python: JWT Bearer Token Flow
import jwt, time, requests
from cryptography.hazmat.primitives import serialization

def get_jwt_access_token(config: dict) -> dict:
    with open(config['private_key_path'], 'rb') as f:
        private_key = serialization.load_pem_private_key(f.read(), password=None)
    
    now = int(time.time())
    payload = {
        'iss': config['client_id'],           # Connected App consumer key
        'sub': config['integration_user'],    # Integration user's username
        'aud': 'https://login.salesforce.com',
        'exp': now + 300,                     # Token valid for 5 minutes only
    }
    assertion = jwt.encode(payload, private_key, algorithm='RS256')
    
    resp = requests.post(
        f"{config['sf_login_url']}/services/oauth2/token",
        data={
            'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
            'assertion' : assertion,
        },
        headers={'Content-Type': 'application/x-www-form-urlencoded'},
    )
    resp.raise_for_status()
    return resp.json()  # { access_token, instance_url, token_type }

				
			

Named Credentials: Decoupling Authentication from Code

Salesforce Named Credentials provide a platform-native mechanism for storing endpoint URLs and authentication credentials in a secure, version-controlled configuration object rather than hardcoding them in Apex code or embedding them in environment variables. For migrations that include Salesforce-to-Salesforce callouts (for example, a post-migration validation Apex class invoking an internal REST service), Named Credentials are the recommended pattern.

Named Credentials eliminate the need to handle OAuth token management in code because the Salesforce platform automatically handles token acquisition, caching, and renewal transparently. An Apex callout that references a Named Credential will always use a valid, non-expired token without any developer-managed refresh logic.

				
					Named Credential Architecture: Apex Callout Flow

  +-------------------------------------------------------------------+
  |                    Salesforce Org                                 |
  |                                                                   |
  |  +------------------+   callout:MigrationAPI/endpoint            |
  |  |   Apex Class     |----------------------------------------->  |
  |  |  (Validator.cls) |                          ^                 |
  |  +------------------+    Named Credential       |                 |
  |                         +-------------------+   |                 |
  |                         | Label: MigrationAPI|  |                 |
  |                         | URL:  https://api  +--+                 |
  |                         | Auth: OAuth 2.0    | (token managed     |
  |                         | (Client Creds)     |  by platform)      |
  |                         +-------------------+                    |
  +-------------------------------------------------------------------+
                                    |
                                    v
                       +------------------------+
                       | External Migration API  |
                       | (REST: HTTPS / TLS 1.3) |
                       +------------------------+


				
			
				
					// Apex: Callout using Named Credential
public class MigrationValidatorCallout {
    private static final String NAMED_CRED = 'callout:Migration_Orchestration_API';
    
    public static Map validateBatch(String batchId) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint(NAMED_CRED + '/v1/batches/' + batchId + '/validate');
        req.setMethod('GET');
        req.setHeader('Content-Type', 'application/json');
        req.setTimeout(30000); // 30-second timeout
        
        Http http = new Http();
        HttpResponse res = http.send(req);
        // Platform automatically injects: Authorization: Bearer 
        
        if (res.getStatusCode() != 200) {
            throw new CalloutException('Validation API returned: ' + res.getStatusCode());
        }
        return (Map) JSON.deserializeUntyped(res.getBody());
    }
}

				
			

Which Flow Fits Which Scenario

ScenarioRecommended Pattern
Automated bulk load from external appClient Credentials Flow
Regulated industry, no stored secrets allowedJWT Bearer Token Flow
Post-migration Apex validation calloutsNamed Credentials
Admin-triggered or interactive migration toolWeb Server Flow (user context required)

Security Checklist Before You Run Anything

A few things to confirm before the first migration job starts:

  • Create a dedicated integration user with only the permissions the migration needs — not a full System Administrator profile
  • Store client_secret and private keys in a secret manager, never in .env files or source code repositories
  • Enable Salesforce Event Monitoring before migration starts, so you capture API-level audit logs from day one
  • Confirm the Connected App’s Permitted Users setting is locked to the migration user
  • Document the Connected App’s expiry settings and add a post-cutover task to deactivate it
  • Run a test token acquisition and a single test API call against a sandbox before going anywhere near production

That last point sounds obvious, but it’s easy to skip when the project timeline is tight. An authentication failure at T-zero of your cutover window is not where you want to diagnose a scope misconfiguration.

What’s Next in This Series

Getting your OAuth setup right is table stakes. The next article is where things get interesting.

Part 3 will cover the Bulk API 2.0 job lifecycle, how to create jobs, stream CSV data at scale, poll for completion without hammering the API, and handle partial failures without losing records or corrupting your load state. If you’ve ever watched a migration job finish and had no idea whether it actually succeeded, that article is for you.

Frequently Asked Questions (FAQ)

Client Credentials Flow authenticates using a client ID and client secret, a shared secret model. JWT Bearer Token Flow uses a signed JWT assertion backed by an RSA key pair, so no shared secret is involved. JWT is the better choice when your security policy prohibits stored secrets or requires certificate-based trust. For most automation scenarios, Client Credentials Flow is simpler to operate.

No. Named Credentials are Apex-native and can only be referenced from Apex code running inside the Salesforce platform. External applications need to handle their own OAuth token management using Client Credentials or JWT Bearer Token flows.

For a Bulk API 2.0 migration, api, bulk_api, and refresh_token are sufficient. Avoid broader scopes like full unless you have a specific reason for them.

If your token expires mid-job, the API calls return a 401 Unauthorized response. The solution is proactive token caching: check the token’s expiry timestamp before each API call and refresh it about 60 seconds before expiry. Bulk API jobs themselves are not interrupted by token expiry; the job runs server-side, but your polling and result-retrieval calls will fail without a valid token.

Deactivate it. Deletion removes the audit record along with the Connected App itself. Deactivation blocks any further token issuance while preserving the configuration and history for compliance purposes.

Kiran Sreeram Prathi
Kiran Sreeram Prathi
Sr. Salesforce Developer  kiransreeram8@live.com

I’m Kiran Sreeram Prathi, a Salesforce Developer dedicated to building scalable, intelligent, and user-focused CRM solutions. Over the past five years, I’ve delivered Salesforce implementations across healthcare, finance, and service industries—focusing on both technical precision and user experience. My expertise spans Lightning Web Components (LWC), Apex, OmniStudio, and Experience Cloud, along with CI/CD automation using GitHub Actions and integrations with platforms such as DocuSign, Conga, and Zpaper. I take pride in transforming complex workflows into seamless digital journeys and implementing clean DevOps strategies that reduce downtime and accelerate delivery. Recognized by organizations like Novartis, WILCO, and Deloitte, I enjoy solving problems that make Salesforce work smarter and scale better. I’m always open to connecting with professionals who are passionate about process transformation, architecture design, and continuous innovation in the Salesforce ecosystem.

Share.
Leave A Reply

Exit mobile version