← All posts
· 4 min read ·
SalesforceSecurityIntegrationApex

Named Credentials and External Credentials: The New Model

API 53 split Named Credentials into two objects. Here's why the old model was a security problem, how the new model works, and how to migrate legacy configurations.

Identity and security concept with lock and credentials

If you’ve been using Salesforce Named Credentials for callouts, the authentication model changed significantly starting in API version 53. The original Named Credential crammed both the endpoint URL and the authentication credentials into one object. The new model separates them: an External Credential holds the authentication configuration, and a Named Credential references it. This post covers why the change was necessary, how the new model works in practice, and what a migration looks like.

The Problem with the Old Model

The original Named Credential design conflated two distinct concerns:

  1. Where to call - the endpoint URL
  2. How to authenticate - credentials, tokens, OAuth config

This caused real problems:

  • OAuth credentials were stored at the org level, so all users making callouts shared the same identity. There was no way to make callouts on behalf of the current user without custom token management.
  • Password-based auth meant usernames and passwords lived in Salesforce metadata. Rotating them required updating the Named Credential and redeploying.
  • There was no principled way to manage per-user authentication for systems that required individual OAuth consent flows.

The New Architecture

The new model introduces a clear separation:

External Credential
  └── Principal (Per-Org or Per-User)
        └── Authentication Protocol (OAuth, JWT, Custom)
              └── Parameters (client_id, client_secret, token URL, etc.)

Named Credential
  └── References External Credential
  └── URL
  └── Callout Options

External Credential - stores the authentication scheme. Think of it as the “how to authenticate” object. It defines whether auth is per-org or per-user, which OAuth flow to use, and holds the credential parameters (ideally referencing Named Principal parameters rather than hardcoding secrets).

Named Credential - stores the endpoint URL and references an External Credential. This is the object you reference in Apex.

Per-User vs Per-Org Principals

The principal type is the most important design decision:

Per-Org principal - a single identity is used for all callouts, regardless of which Salesforce user initiates them. Appropriate for system-to-system integrations where the calling service has one identity (e.g., syncing data to a warehouse).

Per-User principal - each Salesforce user has their own OAuth token. When a user makes a callout, it authenticates as that user in the external system. Requires each user to complete an OAuth authorization flow before making their first callout.

Setting Up OAuth 2.0 Client Credentials Flow

The client credentials flow is the right choice for server-to-server integrations where there’s no user involved in the auth handshake.

In Setup:

  1. Navigate to Named CredentialsExternal Credentials → New
  2. Set Authentication Protocol to OAuth 2.0
  3. Set Identity Type to Named Principal
  4. Add parameters:
    • Authentication URL: https://login.example.com/oauth/token
    • Client ID: your app’s client ID
    • Client Secret: your app’s client secret (mark as Protected)
    • Scope: the required OAuth scopes

Then create a Named Credential that references this External Credential:

  1. Named Credentials → New
  2. Set the URL to your base endpoint (e.g., https://api.example.com)
  3. Set External Credential to the one you just created
  4. Enable Allow Formulas in HTTP Body if needed

Apex Callout Pattern

Nothing changes in how you write Apex callouts - the callout: prefix still works:

public class ExampleApiClient {

    public static String fetchData(String resourcePath) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint('callout:MyExternalAPI' + resourcePath);
        req.setMethod('GET');
        req.setHeader('Accept', 'application/json');
        req.setTimeout(10000);

        Http http = new Http();
        HttpResponse res = http.send(req);

        if (res.getStatusCode() == 200) {
            return res.getBody();
        } else {
            throw new CalloutException(
                'API call failed: ' + res.getStatusCode() + ' ' + res.getStatus()
            );
        }
    }

    public static Map<String, Object> createRecord(Map<String, Object> payload) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint('callout:MyExternalAPI/records');
        req.setMethod('POST');
        req.setHeader('Content-Type', 'application/json');
        req.setBody(JSON.serialize(payload));

        Http http = new Http();
        HttpResponse res = http.send(req);

        if (res.getStatusCode() == 201) {
            return (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
        } else {
            Map<String, Object> err = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
            throw new CalloutException('Failed to create record: ' + err.get('message'));
        }
    }
}

The callout:MyExternalAPI prefix maps to the Named Credential’s API name. The path appended after it is concatenated to the Named Credential’s base URL.

Migrating Legacy Named Credentials

If you have existing Named Credentials using the legacy model (pre-API 53 style with password or OAuth directly in the Named Credential), the migration path is:

  1. Create an External Credential with the same auth configuration that’s currently embedded in your Named Credential
  2. Create a new Named Credential that references the External Credential - use a new API name temporarily
  3. Test callouts through the new Named Credential in a sandbox
  4. Update Apex code to reference the new Named Credential name (a straightforward find-and-replace)
  5. Delete the old Named Credential once all references are updated

The legacy Named Credentials still work - Salesforce hasn’t removed them - but new integrations should always use the split model. The separation makes credential rotation, per-user OAuth, and security auditing significantly more manageable.

What This Means for Deployments

External Credentials and their parameters can be deployed via metadata, but Protected parameters (secrets) are excluded from deployments by design. This means you’ll need a post-deployment step to populate credential values in each environment. Document this in your deployment runbook - it’s a common gap that causes production incidents when someone deploys to a new org and forgets to configure the external credential secrets.

← All posts