# Refresh-token storage (/docs/auth/storage)



A Salesforce refresh token is a long-lived credential. `@kysoql/auth` provides a
small `RefreshTokenStore` interface so rotated tokens can be saved safely.

<SalesforceReference title="OAuth 2.0 Refresh Token Flow for Renewed Sessions" href="https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_refresh_token_flow.htm&language=en_US&type=5" />

Kysoql stores refresh tokens here, not access tokens. Choose storage that matches
your application's security model.

## Store contract [#store-contract]

```ts
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]

Memory storage is useful for tests, short-lived processes, and tools that do not
need refresh state after a restart.

```ts
import { createMemoryRefreshTokenStore } from "@kysoql/auth";

export function createStore(initialRefreshToken: string) {
  return createMemoryRefreshTokenStore(initialRefreshToken);
}
```

## Browser localStorage [#browser-localstorage]

```ts
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 [#redis]

```ts
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 [#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`.

```ts
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.

<Accordions type="single">
  <Accordion title="Client secret transport">
    Set `clientSecretTransport: "basic"` when the Salesforce OAuth client expects
    HTTP Basic authentication. The default sends the secret in the request body.
  </Accordion>

  <Accordion title="Hybrid refresh">
    Set `refreshMode: "hybrid"` for Salesforce hybrid-app refresh semantics.
    Standard refresh is the default.
  </Accordion>

  <Accordion title="Private-key client authentication">
    Pass `clientAssertion` as a callback when each refresh needs a fresh
    `private_key_jwt` assertion. See [private-key JWT](/docs/auth/flows/private-key-jwt).
  </Accordion>
</Accordions>
