
Llegas un lunes a las 9:00 de la mañana y te dicen en la daily que un servidor externo necesita comunicarse con Salesforce: leer registros, modificarlos, ejecutar lógica... Empiezas a revisar la documentación y te encuentras con términos que cuesta entender: connected apps, external client apps, OAuth flows, JWT...
El equipo de ciberseguridad te avisa de que no vale hacerlo de cualquier manera. Te entran sudores, empiezan los nervios y crees que estás ante un dragón de tres cabezas.
El primer reto de cualquier integración servidor a servidor es la autenticación: conseguir que Salesforce confíe en tu servidor y le entregue un access token con el que poder operar. Sin ese token no hay integración posible.
En este artículo veremos las dos estrategias que ofrece Salesforce para este escenario: Client Credentials Flow y JWT Bearer Flow. Cuándo usar cada una, cómo configurarlas y cuál es la diferencia entre ambas en términos de seguridad.
Las integraciones de entrada son comunicaciones que realiza un sistema externo a la organización de Salesforce. Imagina que tenemos una página web en la que se tienen que mostrar los casos de los clientes o un servidor web que realiza un borrado de los clientes que ya no pertenecen a la empresa; ambas opciones son servidores externos que leen datos o ejercen acciones dentro de Salesforce. Una demostración de este tipo de integraciones se encuentra aquí.
Connected Apps era la forma tradicional de crear un punto de entrada a nuestro entorno Salesforce. Sin embargo, desde la actualización de Salesforce Spring 26 se ha bloqueado la creación de nuevas Connected Apps, migrando su funcionalidad a una nueva versión llamada External Client Apps.
Se puede entender fácilmente el funcionamiento de una External Client App si lo vemos como si fuera la puerta de nuestra casa. Es el punto donde podemos dejar entrar a nuestros invitados de forma controlada, y esa puerta se puede abrir de diferentes formas: con una llave, con un código, llamando a la puerta o simplemente dejándola abierta.

Las External Client Apps se pueden gestionar en la sección de External Client App Manager del Setup. A la hora de crear una nueva app te solicitará información básica como un nombre, API name, correo electrónico y teléfono de contacto. Además te pedirá que empieces a configurarla, pero este paso puedes hacerlo posteriormente con más calma.

El acceso a las APIs de Salesforce siempre va a ser mediante el uso de un access token. Es algo similar a tener las llaves de tu casa con la particularidad de que ese acceso va a ser limitado, en tiempo y en espacio. Es decir, va a tener una duración a partir de la cual el acceso va a caducar y va a tener acceso únicamente a las APIs que nosotros le queramos dar.
El modo más extendido de obtener ese access token es el protocolo OAuth. Dispondremos de diferentes estrategias, pero el fin último siempre será el mismo: obtener un access token válido con el que comunicarnos con Salesforce.
Podremos activar el protocolo OAuth en la configuración de la External Client App. En este paso nos solicitará un Callback URL pero solo se usará en situaciones muy específicas; para la mayoría de casos basta con dejar https://localhost.
Una vez activado el OAuth hay que configurar principalmente dos cosas:


La estrategia de Client Credentials Flow hace uso de un valor Client Id, también llamado Consumer Key, y un Client Secret, también llamado Consumer Secret, como si fueran un usuario y una contraseña de la External Client App.
Para acceder a esas credenciales se utiliza el botón Consumer Key and Secret de la configuración del OAuth.
Hay que tener mucha precaución con cómo se almacenan y comparten estas credenciales, ya que cualquier persona que tenga acceso a ellas tendrá acceso al External Client App.
En Salesforce, toda operación se ejecuta dentro del contexto de un usuario. Como el Client Credentials Flow no identifica a ninguno concreto, es necesario definir manualmente uno que actúe como contexto de ejecución para todas las peticiones.
Este usuario se configura en la sección de políticas de la External Client App. Es importante elegirlo con cuidado: todas las transacciones se ejecutarán con sus permisos. Si el usuario no tiene acceso a un objeto o acción concretos, la petición fallará con un error de permisos.

Una vez que tengamos activado el Client Credentials Flow y configurado un usuario de contexto podremos utilizar las credenciales para conseguir el access token de acceso a Salesforce con la siguiente petición.

Variables utilizadas en la petición:
La estrategia de JWT Bearer Flow hace uso de un token en formato JWT, JSON Web Token, firmado con una clave privada para intercambiarlo por el access token.
La estrategia a seguir es:
La configuración del certificado a utilizar se añade en la página de configuración de la External Client App.

Una vez que el servidor ha construido y firmado el JWT con la clave privada podrá enviarlo a Salesforce para intercambiarlo por un access token utilizando la siguiente petición.

Variables utilizadas en la petición:
Un JSON Web Token (JWT) es un estándar abierto para transmitir información de forma segura entre partes como un objeto JSON compacto y firmado digitalmente.
Tiene tres partes separadas por puntos:
El payload de un token JWT tiene típicamente la siguiente información:
{
"iss": "3MVG9...<consumer_key>",
"sub": "usuario@miorg.com",
"aud": "https://login.salesforce.com",
"exp": 1741200000,
"iat": 1741196400
}
La creación y firma del token varía según el lenguaje y la tecnología utilizados. A continuación se muestra una implementación de referencia que sigue el algoritmo recomendado por Salesforce: RS256, de cifrado de clave asimétrica.
/**
* Genera un JWT firmado con la clave privada configurada en env vars.
*
* Un JWT (JSON Web Token) es un objeto compacto que contiene información
* sobre quién hace la petición. Al firmarlo con nuestra clave privada,
* Salesforce puede verificar que realmente somos nosotros usando la clave
* pública que tiene registrada, y a cambio nos entrega un access token.
*/
async function generateJWT(): Promise<string> {
// Leemos la clave privada desde las variables de entorno.
// Esta clave es secreta y nunca debe exponerse al cliente ni subirse al repo.
const rawKey = process.env.JWT_PRIVATE_KEY;
if (!rawKey) throw new Error("JWT_PRIVATE_KEY no configurada");
// Las variables de entorno almacenan los saltos de línea como el literal "\\n"
// en lugar del carácter real. Esta línea los convierte al salto de línea real
// que el formato PEM necesita para ser válido.
const privateKeyPem = rawKey.replace(/\\n/g, "\n");
// Una clave PEM tiene un formato muy concreto: empieza con
// "-----BEGIN PRIVATE KEY-----" y termina con "-----END PRIVATE KEY-----".
// Extraemos ese bloque completo para asegurarnos de que la clave es válida.
const privateKeyMatch = privateKeyPem.match(
/-----BEGIN PRIVATE KEY-----[\s\S]+?-----END PRIVATE KEY-----/
);
if (!privateKeyMatch) throw new Error("Clave privada PEM inválida");
// Convertimos la clave del formato PEM (texto legible) al formato binario
// que la API de criptografía del navegador/Node entiende internamente.
// Pasos:
// 1. Quitamos las cabeceras "-----BEGIN/END PRIVATE KEY-----"
// 2. Quitamos todos los espacios y saltos de línea
// 3. Decodificamos el Base64 resultante a bytes binarios
// 4. Importamos esos bytes como una clave criptográfica de tipo PKCS8
// con el algoritmo RSA-SHA256, lista únicamente para firmar (no para exportar)
const privateKey = await crypto.subtle.importKey(
"pkcs8",
Buffer.from(
privateKeyMatch[0]
.replace(/-----BEGIN PRIVATE KEY-----/, "")
.replace(/-----END PRIVATE KEY-----/, "")
.replace(/\s/g, ""),
"base64"
),
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
false,
["sign"]
);
// Leemos los datos de la organización de Salesforce desde las variables de entorno.
// Estos tres valores formarán el "payload" (cuerpo) del JWT:
// - consumerKey: identifica nuestra aplicación (External Client App) en Salesforce
// - username: el usuario de Salesforce con cuyo contexto se ejecutarán las peticiones
// - audience: la URL de login de Salesforce a la que va dirigido el token
const consumerKey = process.env.SALESFORCE_CONSUMER_KEY;
const username = process.env.SALESFORCE_USERNAME;
const audience =
process.env.SALESFORCE_LOGIN_URL || "https://login.salesforce.com";
if (!consumerKey) {
throw new Error("SALESFORCE_CONSUMER_KEY no configurada");
}
if (!username) {
throw new Error("SALESFORCE_USERNAME no configurada");
}
// Construimos y firmamos el JWT con la librería `jose`.
// Cada método añade un campo estándar al token:
// - setProtectedHeader: indica el algoritmo de firma (RS256 = RSA + SHA-256)
// - setIssuer: "iss" — quién emite el token (nuestra app)
// - setSubject: "sub" — en nombre de qué usuario se hace la petición
// - setAudience: "aud" — a quién va dirigido (Salesforce)
// - setExpirationTime: "exp" — el token caduca en 3 minutos (límite de Salesforce)
// - setIssuedAt: "iat" — marca el momento exacto en que se generó
// - sign: firma todo el contenido con nuestra clave privada
return new SignJWT({})
.setProtectedHeader({ alg: "RS256" })
.setIssuer(consumerKey)
.setSubject(username)
.setAudience(audience)
.setExpirationTime("3m")
.setIssuedAt()
.sign(privateKey);
}
Ambas estrategias de login con la External Client App son formas utilizadas de comunicación de servidor a servidor para integraciones. Pero ambas tienen sus diferencias, ventajas y desventajas.
Client Credentials Flow es mucho más sencillo de manejar y configurar, ya que solo es necesario generar Consumer Key y Consumer Secret y utilizar ambas en peticiones de acceso. Sin embargo, no es recomendable exponer de forma continua el Consumer Secret en cada petición.
El JWT Bearer Flow es más difícil de configurar. Como hemos visto es necesario cierto esfuerzo de creación de clave privada y su certificado con clave pública para compartir con Salesforce, pero con esta estrategia la clave privada nunca sale del servidor externo; lo único que exponemos es el JWT, que tiene un uso temporal.
Es importante tener en cuenta también que con el Client Credentials Flow tenemos que compartir información crítica al servidor, por lo que es necesario hacerlo por un canal seguro. Con el JWT Bearer Flow es el servidor el que comparte el certificado con clave pública con Salesforce, lo que supone un canal seguro también, pero si se expone accidentalmente el certificado tiene mucho menos peligro que si se expone el Consumer Secret.
Teniendo en cuenta todas estas diferencias, Salesforce siempre recomienda utilizar el JWT Bearer Flow para integraciones servidor a servidor de forma segura siempre y cuando el servidor externo sea capaz de generar y firmar el JWT con la configuración adecuada.
Si quieres ver el JWT Bearer Flow en acción, tengo una demo funcional que autentica contra Salesforce en tiempo real: genera el JWT firmado con la clave privada, lo intercambia por un access token y ejecuta una query SOQL. Puedes acceder a la demo aquí.
Las integraciones de entrada en Salesforce pueden parecer complejas al principio, pero una vez comprendidos los mecanismos de autenticación el camino se vuelve claro. La elección entre Client Credentials Flow y JWT Bearer Flow depende del nivel de seguridad requerido y de la capacidad del sistema externo:
En ambos casos, aplica el principio de mínimo privilegio: configura únicamente los OAuth Scopes necesarios y asigna al usuario de contexto los permisos estrictamente imprescindibles.
| Característica | Client Credentials | JWT Bearer |
|---|---|---|
| ¿Requiere secreto? | Sí (Consumer Secret) | No |
| ¿Requiere certificado? | No | Sí (RSA 2048) |
| Revocación inmediata | Sí | No (hasta que expira el JWT) |
| Configuración inicial | Baja | Media-Alta |
| Recomendado para | Integraciones internas rápidas | Entornos sin secretos compartidos |
Con el access token en mano ya tienes la llave en la mano. El siguiente paso es usarla: llamar a las APIs REST de Salesforce para leer registros, crear datos, ejecutar acciones...
En el siguiente artículo vemos exactamente eso: cómo construir peticiones contra la API REST de Salesforce, qué endpoints existen, cómo manejar la renovación del token cuando caduca y un ejemplo real funcionando.
¿Tienes alguna pregunta sobre el artículo? ¡Escríbeme!