Kysoql
Authentication flows

JWT bearer

Use Salesforce OAuth 2.0 JWT bearer authentication for private-key server-to-server integrations.

JWT bearer is the default Kysoql recommendation for a server-to-server integration that can protect a private key. Salesforce validates an RS256-signed JWT against the certificate registered for the external client app and returns an access token for the pre-authorized user.

Authenticate

import { SalesforceAuth } from "@kysoql/auth";

const auth = new SalesforceAuth({
  loginUrl: "https://login.salesforce.com",
  clientId: "external-client-app-id",
});

export function authenticateSalesforce() {
  return auth.jwtBearer({
    username: "integration@example.com",
    privateKey: { type: "file", path: "./salesforce-auth-key.pem" },
  });
}

jwtBearer() creates the Salesforce JWT claims, signs them with RS256, and exchanges the assertion in one call. privateKey is a discriminated object, so the key source is never inferred from a string:

TypeShapeUse when
File{ type: "file", path: string | URL }Kysoql should read a PEM file.
PEM{ type: "pem", value: string }A secret manager or configuration layer already provides PEM text.
CryptoKey{ type: "crypto-key", key: CryptoKey }Your runtime or key-management layer already owns the imported signing key.

PEM values and string file paths are trimmed and validated before use.

The helper defaults the assertion lifetime to 180 seconds and rejects lifetimes above 300 seconds.

Salesforce setup

Configure the external client app for JWT bearer, register the matching X.509 certificate, and authorize the integration user according to Salesforce policy. Salesforce does not issue a refresh token for this flow.

Cache and renew the access token

Salesforce does not issue a refresh token for JWT bearer. A REST client should still reuse the access token it already has instead of exchanging a new JWT for every query. @kysoql/rest does this when accessToken is a provider: it caches the provider result in memory and asks for a replacement only after Salesforce returns 401 INVALID_SESSION_ID.

import { createRestExecutor } from "@kysoql/rest";
import { getSalesforceSession } from "./salesforce-auth";

let session = await getSalesforceSession();
const instanceUrl = session.instanceUrl;

const executor = createRestExecutor({
  instanceUrl,
  accessToken: async ({ refresh }) => {
    if (refresh) {
      const next = await getSalesforceSession();
      if (next.instanceUrl !== instanceUrl) {
        throw new Error("Salesforce instance changed; recreate the REST client.");
      }
      session = next;
    }
    return session.accessToken;
  },
});

Persisting that access token is usually unnecessary: the private key is the long-lived credential, while the access token is replaceable session state. Keep it in memory unless your application has a specific cross-process session-sharing requirement. There is no JWT refresh token to write to refresh-token storage.

For private_key_jwt client authentication used inside another OAuth flow, see private-key client authentication. It is distinct from the JWT bearer grant documented on this page.

On this page