Skip to content

Commit 7c25456

Browse files
committed
fix: rename nonRotating to preserveRefreshToken and fix revokeRefreshToken in online mode
Renamed the internal worker message field `nonRotating` to `preserveRefreshToken` across worker.types.ts, token.worker.ts, http.ts, and api.ts to eliminate the confusing double- negative at the use site (!nonRotating) in security-critical code. Fixed revokeRefreshToken() in online mode: removed the incorrect no-op guard — the /oauth/revoke endpoint accepts Online Refresh Tokens and, because ORTs are session-bound, revocation also terminates the Auth0 session server-side. Updated JSDoc and the sample page to reflect this behaviour accurately.
1 parent 8984fb8 commit 7c25456

6 files changed

Lines changed: 34 additions & 28 deletions

File tree

src/Auth0Client.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,16 +1222,18 @@ export class Auth0Client {
12221222
*
12231223
* If `useRefreshTokens` is disabled, this method does nothing.
12241224
*
1225-
* **Online mode:** when `refreshTokenMode` is `'online'` this method only clears the
1226-
* cached refresh token locally — it does **not** revoke the Online Refresh Token at the
1227-
* authorization server. Online Refresh Tokens are non-rotating and session-bound, and the
1228-
* server does not support token-only revocation for them. To end an online session, use
1229-
* `logout()` instead, which terminates the session and thereby invalidates the ORT.
1225+
* **Online mode:** when `refreshTokenMode` is `'online'`, revoking the ORT via
1226+
* `/oauth/revoke` **also terminates the Auth0 session**. Because Online Refresh Tokens
1227+
* are session-bound, the authorization server ties the token directly to the session —
1228+
* revoking the ORT invalidates the session server-side. This means the user will be
1229+
* required to log in again even if their session cookie has not yet expired.
1230+
* Use this when you need to force a sign-out without a redirect (e.g. background
1231+
* revocation). For a redirect-based sign-out, prefer `logout()`.
12301232
*
12311233
* **Important:** This method revokes the refresh token for a single audience. If your
12321234
* application requests tokens for multiple audiences, each audience may have its own
12331235
* refresh token. To fully revoke all refresh tokens, call this method once per audience.
1234-
* If you want to terminate the user's session entirely, use `logout()` instead.
1236+
* If you want to terminate the user's session with a redirect, use `logout()` instead.
12351237
*
12361238
* When using Multi-Resource Refresh Tokens (MRRT), a single refresh token may cover
12371239
* multiple audiences. In that case, revoking it will affect all cache entries that
@@ -1758,7 +1760,7 @@ export class Auth0Client {
17581760
timeout: this.httpTimeoutMs,
17591761
useMrrt: this.options.useMrrt,
17601762
dpop: this.dpop,
1761-
nonRotating: this.onlineAccess,
1763+
preserveRefreshToken: this.onlineAccess,
17621764
...options,
17631765
scope: scopesToRequest || options.scope,
17641766
},

src/api.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export async function oauthToken(
3838
useFormData,
3939
useMrrt,
4040
dpop,
41-
nonRotating,
41+
preserveRefreshToken,
4242
...options
4343
}: TokenEndpointOptions,
4444
worker?: Worker,
@@ -92,7 +92,7 @@ export async function oauthToken(
9292
isDpopSupported ? dpop : undefined,
9393
undefined,
9494
skipTokenStorage,
95-
nonRotating
95+
preserveRefreshToken
9696
);
9797
}
9898

src/http.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const fetchWithWorker = async (
7474
useFormData?: boolean,
7575
useMrrt?: boolean,
7676
skipTokenStorage?: boolean,
77-
nonRotating?: boolean
77+
preserveRefreshToken?: boolean
7878
) => {
7979
return sendMessage(
8080
{
@@ -89,7 +89,7 @@ const fetchWithWorker = async (
8989
useFormData,
9090
useMrrt,
9191
skipTokenStorage,
92-
nonRotating
92+
preserveRefreshToken
9393
},
9494
worker
9595
);
@@ -105,7 +105,7 @@ export const switchFetch = async (
105105
timeout = DEFAULT_FETCH_TIMEOUT_MS,
106106
useMrrt?: boolean,
107107
skipTokenStorage?: boolean,
108-
nonRotating?: boolean
108+
preserveRefreshToken?: boolean
109109
): Promise<any> => {
110110
if (worker) {
111111
return fetchWithWorker(
@@ -118,7 +118,7 @@ export const switchFetch = async (
118118
useFormData,
119119
useMrrt,
120120
skipTokenStorage,
121-
nonRotating
121+
preserveRefreshToken
122122
);
123123
} else {
124124
return fetchWithoutWorker(fetchUrl, fetchOptions, timeout);
@@ -137,7 +137,7 @@ export async function getJSON<T>(
137137
dpop?: Pick<Dpop, 'generateProof' | 'getNonce' | 'setNonce'>,
138138
isDpopRetry?: boolean,
139139
skipTokenStorage?: boolean,
140-
nonRotating?: boolean
140+
preserveRefreshToken?: boolean
141141
): Promise<T> {
142142
if (dpop) {
143143
const dpopProof = await dpop.generateProof({
@@ -164,7 +164,7 @@ export async function getJSON<T>(
164164
timeout,
165165
useMrrt,
166166
skipTokenStorage,
167-
nonRotating
167+
preserveRefreshToken
168168
);
169169
fetchError = null;
170170
break;
@@ -239,7 +239,7 @@ export async function getJSON<T>(
239239
dpop,
240240
true, // !
241241
skipTokenStorage,
242-
nonRotating
242+
preserveRefreshToken
243243
);
244244
}
245245

src/worker/token.worker.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ const checkDownscoping = (scope: string, audience: string): boolean => {
8282
}
8383

8484
const messageHandler = async ({
85-
data: { timeout, auth, fetchUrl, fetchOptions, useFormData, useMrrt, skipTokenStorage, nonRotating },
85+
data: { timeout, auth, fetchUrl, fetchOptions, useFormData, useMrrt, skipTokenStorage, preserveRefreshToken },
8686
ports: [port]
8787
}: MessageEvent<WorkerRefreshTokenMessage>) => {
8888
let headers: FetchResponse['headers'] = {};
@@ -188,9 +188,7 @@ const messageHandler = async ({
188188

189189
setRefreshToken(json.refresh_token, audience, scope);
190190
delete json.refresh_token;
191-
} else if (!nonRotating) {
192-
// Non-rotating (online) refresh tokens come back without a replacement;
193-
// keep the stored ORT instead of evicting it.
191+
} else if (!preserveRefreshToken) {
194192
deleteRefreshToken(audience, scope);
195193
}
196194

src/worker/worker.types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ export type WorkerRefreshTokenMessage = WorkerTokenMessage & {
2121
useMrrt?: boolean;
2222
skipTokenStorage?: boolean;
2323
/**
24-
* Non-rotating refresh token (Online Refresh Token). When set, the worker keeps
25-
* the stored token if the response carries no replacement, instead of deleting it.
24+
* When true the worker keeps the stored token if the response carries no replacement
25+
* (Online Refresh Tokens are non-rotating and never come back with a new token).
2626
*/
27-
nonRotating?: boolean;
27+
preserveRefreshToken?: boolean;
2828
};
2929

3030
export type WorkerRevokeTokenMessage = Omit<WorkerTokenMessage, 'auth'> & {

static/online-access.html

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -390,12 +390,16 @@ <h2>Access token (last exchange)</h2>
390390
await refreshDiagnostics(client);
391391
}
392392

393-
// Calls revokeRefreshToken and reports the raw outcome. Online: clears the local cache only,
394-
// the ORT is NOT revoked server-side. Offline: revokes the rotating RT at the server.
393+
// Calls revokeRefreshToken.
394+
// Online (memory/worker mode): the ORT lives in the worker's in-memory store.
395+
// revokeToken sends a 'revoke' message to the worker → worker calls /oauth/revoke
396+
// with the ORT → deletes it from its store. Server-side revocation works.
397+
// Offline: rotating RT read from ICache → revoked at server.
395398
async function revoke(client) {
396399
var before = readRefreshTokenFromCache();
397400
tokenOutEl.textContent = 'Revoking refresh token…';
398401

402+
var t0 = performance.now();
399403
var error;
400404
try {
401405
// The RT is cached under the login audience; pass it so the lookup finds the token.
@@ -404,6 +408,7 @@ <h2>Access token (last exchange)</h2>
404408
} catch (err) {
405409
error = err instanceof Error ? err.message : String(err);
406410
}
411+
var ms = Math.round(performance.now() - t0);
407412

408413
var after = readRefreshTokenFromCache();
409414

@@ -412,13 +417,14 @@ <h2>Access token (last exchange)</h2>
412417
{
413418
mode: mode,
414419
note: mode === 'online'
415-
? 'Online: local cache cleared only; ORT NOT revoked server-side. Use logout() to end the session.'
420+
? 'Online (worker mode): ORT revoked at /oauth/revoke via worker. getTokenSilently should now fail.'
416421
: 'Offline: rotating refresh token revoked at the server.',
422+
durationMs: ms,
417423
refreshTokenBefore: mask(before),
418424
refreshTokenAfter: mask(after),
419-
changed: before !== after
425+
cacheChanged: before !== after
420426
},
421-
error ? { error: error } : null
427+
error ? { error: error } : { success: true }
422428
),
423429
null, 2
424430
);

0 commit comments

Comments
 (0)