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:
| Type | Shape | Use 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.