Skip to content

Commit f40a133

Browse files
docs: update examples and README to use RefreshTokenMode enum for online access
1 parent e2a4151 commit f40a133

7 files changed

Lines changed: 81 additions & 76 deletions

File tree

EXAMPLES.md

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -219,22 +219,24 @@ This makes ORTs a good fit for SPAs that want a refresh-token renewal path whose
219219
220220
### Enabling Online Access
221221

222-
Set `refreshTokenMode: 'online'` together with `useRefreshTokens: true` and `useDpop: true`:
222+
Set `refreshTokenMode` to `RefreshTokenMode.Online` together with `useRefreshTokens: true` and `useDpop: true`:
223223

224224
```js
225+
import { createAuth0Client, RefreshTokenMode } from '@auth0/auth0-spa-js';
226+
225227
const auth0 = await createAuth0Client({
226228
domain: '<AUTH0_DOMAIN>',
227229
clientId: '<AUTH0_CLIENT_ID>',
228230
useRefreshTokens: true, // required — online access is a refresh-token grant
229-
refreshTokenMode: 'online',
231+
refreshTokenMode: RefreshTokenMode.Online,
230232
useDpop: true, // required — DPoP is mandatory for online access
231233
authorizationParams: {
232234
redirect_uri: '<MY_CALLBACK_URL>'
233235
}
234236
});
235237
```
236238

237-
`refreshTokenMode` is a sub-option of `useRefreshTokens`. It defaults to `'offline'` (the rotating [offline refresh tokens](#refresh-tokens) described above); setting it to `'online'` opts into Online Refresh Tokens.
239+
`refreshTokenMode` is a sub-option of `useRefreshTokens`. It defaults to `RefreshTokenMode.Offline` (the rotating [offline refresh tokens](#refresh-tokens) described above); setting it to `RefreshTokenMode.Online` opts into Online Refresh Tokens. Always reference the exported `RefreshTokenMode` enum rather than hard-coding the mode.
238240

239241
Enabling this option causes the SDK to:
240242

@@ -243,16 +245,16 @@ Enabling this option causes the SDK to:
243245
- Store the non-rotating ORT in the existing cache and reuse it on every refresh, never replacing it.
244246

245247
> [!NOTE]
246-
> Online access is **opt-in**. When `refreshTokenMode` is unset or `'offline'`, the SDK behaves exactly as before.
248+
> Online access is **opt-in**. When `refreshTokenMode` is unset or `RefreshTokenMode.Offline`, the SDK behaves exactly as before.
247249
248250
> [!NOTE]
249251
> This feature requires the `online_refresh_tokens` flag to be enabled for your tenant and `allow_online_access` to be enabled on the resource server (on by default).
250252
251-
### `refreshTokenMode: 'offline'` vs. `'online'`
253+
### `RefreshTokenMode.Offline` vs. `RefreshTokenMode.Online`
252254

253-
`refreshTokenMode` selects which refresh-token type the refresh-token grant uses. It is a sub-option of `useRefreshTokens` (which must be `true` for either mode) and defaults to `'offline'`:
255+
`refreshTokenMode` selects which refresh-token type the refresh-token grant uses. It is a sub-option of `useRefreshTokens` (which must be `true` for either mode) and defaults to `RefreshTokenMode.Offline`:
254256

255-
| | `refreshTokenMode: 'offline'` (default) | `refreshTokenMode: 'online'` |
257+
| | `RefreshTokenMode.Offline` (default) | `RefreshTokenMode.Online` |
256258
| --- | --- | --- |
257259
| Requires | `useRefreshTokens: true` | `useRefreshTokens: true` + `useDpop: true` |
258260
| Scope injected | `offline_access` | `online_access` |
@@ -266,32 +268,34 @@ The two modes inject mutually exclusive scopes (`offline_access` vs. `online_acc
266268

267269
The SDK enforces the DPoP requirement at two layers:
268270

269-
1. **Compile-time (TypeScript).** When you call `createAuth0Client` with `refreshTokenMode` set to the literal `'online'`, the compiler requires both `useRefreshTokens: true` and `useDpop: true`:
271+
1. **Compile-time (TypeScript).** When you call `createAuth0Client` with `refreshTokenMode: RefreshTokenMode.Online`, the compiler requires both `useRefreshTokens: true` and `useDpop: true`:
270272

271273
```ts
272-
// ❌ compile error: `useRefreshTokens: true` and `useDpop: true` are required when `refreshTokenMode: 'online'`
273-
createAuth0Client({ domain, clientId, refreshTokenMode: 'online' });
274+
import { createAuth0Client, RefreshTokenMode } from '@auth0/auth0-spa-js';
275+
276+
// ❌ compile error: `useRefreshTokens: true` and `useDpop: true` are required for online mode
277+
createAuth0Client({ domain, clientId, refreshTokenMode: RefreshTokenMode.Online });
274278

275279
// ❌ compile error: `useDpop: true` is still required
276-
createAuth0Client({ domain, clientId, refreshTokenMode: 'online', useRefreshTokens: true });
280+
createAuth0Client({ domain, clientId, refreshTokenMode: RefreshTokenMode.Online, useRefreshTokens: true });
277281

278282
// ✅ valid
279-
createAuth0Client({ domain, clientId, refreshTokenMode: 'online', useRefreshTokens: true, useDpop: true });
283+
createAuth0Client({ domain, clientId, refreshTokenMode: RefreshTokenMode.Online, useRefreshTokens: true, useDpop: true });
280284
```
281285

282286
> [!NOTE]
283-
> The compile-time check only narrows when `refreshTokenMode` is the **literal** `'online'`. A dynamically-typed value (e.g. `refreshTokenMode: someString`), an `as any` cast, or plain JavaScript all bypass it — which is why the runtime check below exists too.
287+
> The compile-time check narrows on the online mode value. A dynamically-typed value (e.g. a `refreshTokenMode` read from config at runtime), an `as any` cast, or plain JavaScript all bypass it — which is why the runtime check below exists too.
284288
285-
2. **Runtime (all consumers, including plain JS).** The `Auth0Client` constructor throws an `InvalidConfigurationError` when `refreshTokenMode: 'online'` but `useRefreshTokens` or `useDpop` is not `true`. The error's `suggestion` tells you exactly which option to set:
289+
2. **Runtime (all consumers, including plain JS).** The `Auth0Client` constructor throws an `InvalidConfigurationError` when online mode is requested but `useRefreshTokens` or `useDpop` is not `true`. The error's `suggestion` tells you exactly which option to set:
286290

287291
```js
288-
import { InvalidConfigurationError } from '@auth0/auth0-spa-js';
292+
import { createAuth0Client, RefreshTokenMode, InvalidConfigurationError } from '@auth0/auth0-spa-js';
289293

290294
try {
291295
const auth0 = await createAuth0Client({
292296
domain: '<AUTH0_DOMAIN>',
293297
clientId: '<AUTH0_CLIENT_ID>',
294-
refreshTokenMode: 'online',
298+
refreshTokenMode: RefreshTokenMode.Online,
295299
useRefreshTokens: true // missing useDpop: true
296300
});
297301
} catch (e) {
@@ -317,11 +321,13 @@ After logout, the ORT is no longer valid; a subsequent `getTokenSilently()` fall
317321
Online access is compatible with [MRRT](#using-multi-resource-refresh-tokens): a single ORT can be exchanged for access tokens across the audiences allowed by your refresh-token policies. The ORT remains non-rotating throughout — the same token is reused for every cross-audience exchange.
318322

319323
```js
324+
import { createAuth0Client, RefreshTokenMode } from '@auth0/auth0-spa-js';
325+
320326
const auth0 = await createAuth0Client({
321327
domain: '<AUTH0_DOMAIN>',
322328
clientId: '<AUTH0_CLIENT_ID>',
323329
useRefreshTokens: true,
324-
refreshTokenMode: 'online',
330+
refreshTokenMode: RefreshTokenMode.Online,
325331
useDpop: true,
326332
useMrrt: true,
327333
authorizationParams: {

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,22 +116,24 @@ window.addEventListener('load', async () => {
116116

117117
### Online Access
118118

119-
Set `refreshTokenMode: 'online'` (together with the required `useRefreshTokens: true` and `useDpop: true`) to use **Online Refresh Tokens** — non-rotating refresh tokens bound to the Auth0 session lifetime. The SDK injects the `online_access` scope and routes renewal through the refresh-token grant.
119+
Set `refreshTokenMode` to `RefreshTokenMode.Online` (together with the required `useRefreshTokens: true` and `useDpop: true`) to use **Online Refresh Tokens** — non-rotating refresh tokens bound to the Auth0 session lifetime. The SDK injects the `online_access` scope and routes renewal through the refresh-token grant.
120120

121121
```js
122+
import { createAuth0Client, RefreshTokenMode } from '@auth0/auth0-spa-js';
123+
122124
const auth0 = await createAuth0Client({
123125
domain: '<AUTH0_DOMAIN>',
124126
clientId: '<AUTH0_CLIENT_ID>',
125127
useRefreshTokens: true,
126-
refreshTokenMode: 'online',
128+
refreshTokenMode: RefreshTokenMode.Online,
127129
useDpop: true,
128130
authorizationParams: {
129131
redirect_uri: '<MY_CALLBACK_URL>'
130132
}
131133
});
132134
```
133135

134-
`refreshTokenMode` is a sub-option of `useRefreshTokens`: it defaults to `'offline'` (the rotating refresh tokens described above) and must be set to `'online'` for Online Refresh Tokens. Online mode requires both `useRefreshTokens: true` and `useDpop: true`. See [Online Access](https://github.com/auth0/auth0-spa-js/blob/main/EXAMPLES.md#online-access-online-refresh-tokens) in EXAMPLES.md for the full guide.
136+
`refreshTokenMode` is a sub-option of `useRefreshTokens`: it defaults to `RefreshTokenMode.Offline` (the rotating refresh tokens described above) and must be set to `RefreshTokenMode.Online` for Online Refresh Tokens. Online mode requires both `useRefreshTokens: true` and `useDpop: true`. See [Online Access](https://github.com/auth0/auth0-spa-js/blob/main/EXAMPLES.md#online-access-online-refresh-tokens) in EXAMPLES.md for the full guide.
135137

136138
### More Examples
137139

__tests__/Auth0Client/onlineAccess.types.test.ts

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
/**
2-
* Compile-time enforcement of the online-access option, via `createAuth0Client`
3-
* overloads (the interface itself stays plain, non-breaking optional fields).
4-
*
5-
* These assertions are validated by ts-jest's type checker at test time: each
6-
* `@ts-expect-error` MUST sit over a genuine type error, or TypeScript reports an
7-
* unused-directive error and the suite fails. The runtime safety net (the
8-
* `InvalidConfigurationError` throw) is covered separately in onlineAccess.test.ts.
9-
*
10-
* The overloads only narrow on a literal `refreshTokenMode: 'online'`, so these cover
11-
* the common literal mistakes a TS user would make at the call site.
2+
* Compile-time enforcement of the online-access overloads, type-checked by ts-jest:
3+
* each `@ts-expect-error` must sit over a genuine type error. The runtime safety net
4+
* is covered in onlineAccess.test.ts.
125
*/
13-
import { createAuth0Client } from '../../src';
6+
import { createAuth0Client, RefreshTokenMode } from '../../src';
147

158
const base = {
169
domain: 'example.auth0.com',
@@ -19,10 +12,17 @@ const base = {
1912

2013
describe('createAuth0Client — online access type enforcement', () => {
2114
it('requires useRefreshTokens: true and useDpop: true when refreshTokenMode is online', () => {
22-
// Compile-only — never invoked. Wrapped in a never-true guard so the calls
23-
// are type-checked but do not execute (no DOM/crypto needed).
15+
// Compile-only — type-checked but never executed.
2416
const _typecheck = async () => {
25-
// Valid combo.
17+
// Valid combo, using the RefreshTokenMode enum.
18+
await createAuth0Client({
19+
...base,
20+
refreshTokenMode: RefreshTokenMode.Online,
21+
useRefreshTokens: true,
22+
useDpop: true
23+
});
24+
25+
// Raw string still accepted for backward compatibility.
2626
await createAuth0Client({
2727
...base,
2828
refreshTokenMode: 'online',
@@ -31,22 +31,22 @@ describe('createAuth0Client — online access type enforcement', () => {
3131
});
3232

3333
// Missing useRefreshTokens → compile error.
34-
// @ts-expect-error refreshTokenMode: 'online' requires useRefreshTokens: true
35-
await createAuth0Client({ ...base, refreshTokenMode: 'online', useDpop: true });
34+
// @ts-expect-error online mode requires useRefreshTokens: true
35+
await createAuth0Client({ ...base, refreshTokenMode: RefreshTokenMode.Online, useDpop: true });
3636

3737
// Missing useDpop → compile error.
38-
// @ts-expect-error refreshTokenMode: 'online' requires useDpop: true
38+
// @ts-expect-error online mode requires useDpop: true
3939
await createAuth0Client({
4040
...base,
41-
refreshTokenMode: 'online',
41+
refreshTokenMode: RefreshTokenMode.Online,
4242
useRefreshTokens: true
4343
});
4444

4545
// useDpop: false → compile error.
46-
// @ts-expect-error refreshTokenMode: 'online' requires useDpop: true (not false)
46+
// @ts-expect-error online mode requires useDpop: true (not false)
4747
await createAuth0Client({
4848
...base,
49-
refreshTokenMode: 'online',
49+
refreshTokenMode: RefreshTokenMode.Online,
5050
useRefreshTokens: true,
5151
useDpop: false
5252
});
@@ -60,6 +60,8 @@ describe('createAuth0Client — online access type enforcement', () => {
6060
const _typecheck = async () => {
6161
// Legacy combos remain valid — the overloads must not constrain them.
6262
await createAuth0Client({ ...base, useRefreshTokens: true, useDpop: false });
63+
await createAuth0Client({ ...base, refreshTokenMode: RefreshTokenMode.Offline, useRefreshTokens: true });
64+
// Raw string still accepted for backward compatibility.
6365
await createAuth0Client({ ...base, refreshTokenMode: 'offline', useRefreshTokens: true });
6466
};
6567
void _typecheck;

src/Auth0Client.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,7 @@ export class Auth0Client {
195195
refreshTokenMode: 'offline',
196196
};
197197

198-
/**
199-
* Validates the online-access configuration and returns whether online mode is enabled.
200-
*
201-
* Online mode (`refreshTokenMode: 'online'`) is a refresh-token grant, so it requires
202-
* `useRefreshTokens: true`, and it requires DPoP, so `useDpop: true` is mandatory.
203-
*/
198+
/** Validates online-access config and returns whether online mode is enabled. */
204199
private resolveOnlineAccess(options: Auth0ClientOptions): boolean {
205200

206201
if (options.refreshTokenMode !== 'online') {
@@ -285,12 +280,7 @@ export class Auth0Client {
285280
? this.cookieStorage
286281
: SessionStorage;
287282

288-
// Construct the scopes based on the following:
289-
// 1. Always include `openid`
290-
// 2. Include the scopes provided in `authorizationParams. This defaults to `profile email`
291-
// 3. Add `online_access` when in online-access mode, otherwise `offline_access` if
292-
// `useRefreshTokens` is enabled. Online and offline are mutually exclusive — online
293-
// must never inject `offline_access`.
283+
// `online_access` and `offline_access` are mutually exclusive — inject at most one.
294284
this.scope = injectDefaultScopes(
295285
this.options.authorizationParams.scope,
296286
'openid',
@@ -353,7 +343,6 @@ export class Auth0Client {
353343
);
354344

355345
// Don't use web workers unless using refresh tokens in memory.
356-
// Online mode requires `useRefreshTokens: true`, so it is covered here too.
357346
if (
358347
typeof window !== 'undefined' &&
359348
window.Worker &&
@@ -1511,9 +1500,8 @@ export class Auth0Client {
15111500
}
15121501
);
15131502

1514-
// If is refreshed with MRRT, we update all entries that have the old
1515-
// refresh_token with the new one if the server responded with one.
1516-
// Online Refresh Tokens are non-rotating, so the stored RT is never replaced.
1503+
// Propagate a rotated RT to all MRRT entries. Skipped for online mode,
1504+
// where ORTs are non-rotating.
15171505
if (
15181506
!this.onlineAccess &&
15191507
tokenResult.refresh_token &&

src/errors.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,8 @@ export class GenericError extends Error {
2929
}
3030

3131
/**
32-
* Thrown at construction time when the Auth0Client is configured with an invalid combination
33-
* of options (e.g. `refreshTokenMode: 'online'` without `useDpop: true`). Carries a `suggestion` with
34-
* the exact configuration change to make.
32+
* Thrown at construction time for an invalid option combination (e.g.
33+
* `refreshTokenMode: 'online'` without `useDpop: true`). The `suggestion` names the fix.
3534
*/
3635
export class InvalidConfigurationError extends GenericError {
3736
constructor(message: string, public suggestion: string) {

src/global.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,22 @@ import { CompleteResponse } from './myaccount';
99
*/
1010
export type InteractiveErrorHandler = 'popup';
1111

12+
/**
13+
* The refresh-token variant used when `useRefreshTokens: true`.
14+
*
15+
* - {@link RefreshTokenMode.Offline} (default): rotating offline refresh tokens.
16+
* - {@link RefreshTokenMode.Online}: non-rotating Online Refresh Tokens (ORTs).
17+
*/
18+
// `const` object, not a TS `enum`, so members stay string-literal types and
19+
// remain assignable to the `createAuth0Client` overloads.
20+
export const RefreshTokenMode = {
21+
Offline: 'offline',
22+
Online: 'online'
23+
} as const;
24+
25+
export type RefreshTokenMode =
26+
(typeof RefreshTokenMode)[keyof typeof RefreshTokenMode];
27+
1228
export interface AuthorizationParams {
1329
/**
1430
* - `'page'`: displays the UI with a full page view
@@ -314,21 +330,15 @@ export interface Auth0ClientOptions {
314330

315331
/**
316332
* Selects the refresh-token variant used when `useRefreshTokens: true`.
333+
* Defaults to {@link RefreshTokenMode.Offline}.
317334
*
318-
* - `'offline'` (default): rotating offline refresh tokens, requested via the
319-
* `offline_access` scope. This is the existing behavior.
320-
* - `'online'`: non-rotating Online Refresh Tokens (ORTs), bound to the Auth0
321-
* session lifetime, requested via the `online_access` scope. When the session
322-
* ends or is revoked, the ORT becomes invalid.
323-
*
324-
* Online mode requires `useRefreshTokens: true` (it is a kind of refresh-token
325-
* grant) and `useDpop: true` (DPoP is mandatory for online access). The
326-
* `online_access` and `offline_access` scopes are mutually exclusive — online
327-
* mode never injects `offline_access`.
335+
* - {@link RefreshTokenMode.Offline} (default): rotating offline refresh tokens (`offline_access`).
336+
* - {@link RefreshTokenMode.Online}: non-rotating, session-bound Online Refresh Tokens (`online_access`).
328337
*
329-
* Defaults to `'offline'` — when unset the SDK behaves exactly as before.
338+
* Online mode requires `useRefreshTokens: true` and `useDpop: true`. The
339+
* `online_access` and `offline_access` scopes are mutually exclusive.
330340
*/
331-
refreshTokenMode?: 'offline' | 'online';
341+
refreshTokenMode?: RefreshTokenMode;
332342

333343
/**
334344
* Configures automatic handling of interactive authentication errors.

src/index.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ import './global';
66
export * from './global';
77

88
/**
9-
* Online mode (`refreshTokenMode: 'online'`) is a refresh-token grant and requires DPoP.
10-
* When `refreshTokenMode` is the literal `'online'`, the compiler requires both
11-
* `useRefreshTokens: true` and `useDpop: true`. This only narrows on the literal `'online'`;
12-
* dynamic values, casts, and plain JS are covered by the runtime check in the
13-
* `Auth0Client` constructor.
9+
* Online mode requires `useRefreshTokens: true` and `useDpop: true`, enforced here at
10+
* compile time. Dynamic values, casts, and plain JS are covered by the runtime check in
11+
* the `Auth0Client` constructor.
1412
*/
1513
export async function createAuth0Client(
1614
options: Auth0ClientOptions & {

0 commit comments

Comments
 (0)