Refresh-token storage
Persist Salesforce refresh-token rotation in memory, localStorage, Redis, or your own store.
A Salesforce refresh token is a long-lived credential. @kysoql/auth provides a
small RefreshTokenStore interface so rotated tokens can be saved safely.
Kysoql stores refresh tokens here, not access tokens. Choose storage that matches your application's security model.
Store contract
import type { RefreshTokenStore } from "@kysoql/auth";
export function encryptedStore(): RefreshTokenStore {
let encrypted: string | undefined;
return {
getRefreshToken: async () => encrypted,
setRefreshToken: async (token) => {
encrypted = token;
},
deleteRefreshToken: async () => {
encrypted = undefined;
},
};
}A production store should protect tokens appropriately for its platform. If saving a rotated token fails, let the error propagate. Returning a new access token while losing its replacement refresh token can leave the session unable to refresh again.
Memory
Memory storage is useful for tests, short-lived processes, and tools that do not need refresh state after a restart.
import { createMemoryRefreshTokenStore } from "@kysoql/auth";
export function createStore(initialRefreshToken: string) {
return createMemoryRefreshTokenStore(initialRefreshToken);
}Browser localStorage
import { createLocalStorageRefreshTokenStore } from "@kysoql/auth";
const store = createLocalStorageRefreshTokenStore({
key: "salesforce:refresh-token",
});The browser adapter uses globalThis.localStorage by default. You can pass a
Web Storage-compatible storage object for tests or other runtimes.
localStorage is readable by JavaScript running on the page. Only use it when
that exposure fits your threat model. For multi-user apps, use a separate key per
application session rather than sharing one token key across users.
Redis
import { createRedisRefreshTokenStore, type RedisLike } from "@kysoql/auth";
export function createStore(client: RedisLike, sessionId: string) {
return createRedisRefreshTokenStore({
client,
key: sessionId,
prefix: "my-app:salesforce:refresh:",
});
}The Redis adapter only needs get, set, and del. Kysoql does not add a TTL
or distributed lock. Configure expiry, encryption, ACLs, and concurrency control
at the application or infrastructure layer when you need them.
Use a stored token with REST
SalesforceAuth.storedRefreshToken() turns a store into a reusable session and
an accessTokenProvider that can be passed straight to @kysoql/rest.
import {
createRedisRefreshTokenStore,
SalesforceAuth,
type RedisLike,
} from "@kysoql/auth";
import { createRestExecutor } from "@kysoql/rest";
export async function createExecutor(
client: RedisLike,
initialRefreshToken: string,
) {
const auth = new SalesforceAuth({
loginUrl: "https://example.my.salesforce.com",
clientId: "oauth-client-id",
});
const sessionAuth = auth.storedRefreshToken({
initialRefreshToken,
refreshTokenStore: createRedisRefreshTokenStore({
client,
key: "integration-user",
}),
});
const session = await sessionAuth.getSession();
return createRestExecutor({
instanceUrl: session.instanceUrl,
accessToken: sessionAuth.accessTokenProvider,
});
}If the store already contains a token, that token takes precedence over
initialRefreshToken. Refresh calls are single-flighted within one JavaScript
process and rotated tokens are saved before the new session is returned.
Set clientSecretTransport: "basic" when the Salesforce OAuth client expects
HTTP Basic authentication. The default sends the secret in the request body.
Set refreshMode: "hybrid" for Salesforce hybrid-app refresh semantics.
Standard refresh is the default.
Pass clientAssertion as a callback when each refresh needs a fresh
private_key_jwt assertion. See private-key JWT.