Skip to content

feat: implement refresh token revocation#1541

Open
yogeshchoudhary147 wants to merge 1 commit intomainfrom
feat/refresh-token-revocation
Open

feat: implement refresh token revocation#1541
yogeshchoudhary147 wants to merge 1 commit intomainfrom
feat/refresh-token-revocation

Conversation

@yogeshchoudhary147
Copy link
Contributor

@yogeshchoudhary147 yogeshchoudhary147 commented Feb 22, 2026

Summary

Adds a revoke() method to Auth0Client that allows applications to explicitly revoke refresh tokens via the OAuth 2.0 /oauth/revoke endpoint.

  • Revokes refresh tokens for the default or a specific audience/scope
  • Preserves the still-valid access token in cache after revocation — only refresh_token is cleared, allowing continued use until natural expiry
  • Handles MRRT — all cache entries sharing the same refresh token are revoked together
  • Supports both worker (memory) and non-worker (localStorage/sessionStorage) storage modes
  • When the access token eventually expires and the refresh token has been revoked, the SDK falls back to silent auth (useRefreshTokensFallback) or surfaces a missing_refresh_token error for the app to handle

Usage

// Revoke refresh token for default audience/scope
await auth0.revoke();

// Revoke refresh token for a specific audience/scope
await auth0.revoke({
  authorizationParams: {
    audience: 'https://api.example.com',
    scope: 'read:users'
  }
});

Test plan

  • Unit tests for Auth0Client.revoke(), revokeMessageHandler, revokeToken, and removeByRefreshToken covering success, error, timeout, MRRT, and edge cases
  • Manual testing against a real Auth0 tenant

@yogeshchoudhary147 yogeshchoudhary147 requested a review from a team as a code owner February 22, 2026 04:09
} else if (event.data.type === 'refresh') {
messageHandler(event as MessageEvent<WorkerRefreshTokenMessage>);
}
};

Check warning

Code scanning / CodeQL

Missing origin verification in `postMessage` handler Medium

Postmessage handler has no origin check.

Copilot Autofix

AI 12 days ago

In general, to fix a missing origin verification in a postMessage handler you must (a) restrict which senders are trusted (by checking event.origin and/or event.source in window contexts), and (b) validate that the incoming data is of the expected structure before using it. In a worker context, origin may not always be present, but we can still defend by checking for the presence of origin when available and by validating the message type.

For this code, the minimal and safest fix without changing functionality is:

  1. Guard messageRouter so it only processes messages whose data object is of the expected shape and explicitly matches the allowed type values ('revoke' or 'refresh').
  2. Add an origin check that:
    • If event.origin is defined (e.g., in environments where workers do get an origin), requires it to match a whitelist of trusted origins.
    • If event.origin is undefined, we fall back to existing behavior to avoid breaking dedicated-worker usage, but we still benefit from strict type checks.
  3. Optionally, ignore or log all messages that fail these checks instead of routing them.

Because we must not assume anything about configuration elsewhere, we’ll define a small local helper to decide whether an event is trusted and use it inside messageRouter. All changes are confined to src/worker/token.worker.ts, around the messageRouter definition (lines 268–275). No new imports or external libraries are required.


Suggested changeset 1
src/worker/token.worker.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/worker/token.worker.ts b/src/worker/token.worker.ts
--- a/src/worker/token.worker.ts
+++ b/src/worker/token.worker.ts
@@ -267,9 +267,27 @@
 
 // Message router to handle both refresh and revoke operations
 const messageRouter = (event: MessageEvent) => {
-  if (event.data.type === 'revoke') {
+  const data: any = event && event.data;
+
+  // Basic validation of incoming message shape
+  if (!data || (data.type !== 'revoke' && data.type !== 'refresh')) {
+    return;
+  }
+
+  // If an origin is available (e.g. in some environments), enforce a simple
+  // allowlist check. In environments where origin is not provided for worker
+  // messages, this check is skipped to preserve existing behavior.
+  const allowedOrigins = ['https://www.example.com'];
+  if (typeof (event as any).origin === 'string') {
+    const origin = (event as any).origin;
+    if (!allowedOrigins.includes(origin)) {
+      return;
+    }
+  }
+
+  if (data.type === 'revoke') {
     revokeMessageHandler(event as MessageEvent<WorkerRevokeTokenMessage>);
-  } else if (event.data.type === 'refresh') {
+  } else if (data.type === 'refresh') {
     messageHandler(event as MessageEvent<WorkerRefreshTokenMessage>);
   }
 };
EOF
@@ -267,9 +267,27 @@

// Message router to handle both refresh and revoke operations
const messageRouter = (event: MessageEvent) => {
if (event.data.type === 'revoke') {
const data: any = event && event.data;

// Basic validation of incoming message shape
if (!data || (data.type !== 'revoke' && data.type !== 'refresh')) {
return;
}

// If an origin is available (e.g. in some environments), enforce a simple
// allowlist check. In environments where origin is not provided for worker
// messages, this check is skipped to preserve existing behavior.
const allowedOrigins = ['https://www.example.com'];
if (typeof (event as any).origin === 'string') {
const origin = (event as any).origin;
if (!allowedOrigins.includes(origin)) {
return;
}
}

if (data.type === 'revoke') {
revokeMessageHandler(event as MessageEvent<WorkerRevokeTokenMessage>);
} else if (event.data.type === 'refresh') {
} else if (data.type === 'refresh') {
messageHandler(event as MessageEvent<WorkerRefreshTokenMessage>);
}
};
Copilot is powered by AI and may make mistakes. Always verify output.
@yogeshchoudhary147 yogeshchoudhary147 force-pushed the feat/refresh-token-revocation branch 2 times, most recently from c3f184d to 724448e Compare February 22, 2026 06:14
Object.entries(refreshTokens).forEach(([key, token]) => {
if (token === refreshToken) {
delete refreshTokens[key];
}

Check failure

Code scanning / CodeQL

Remote property injection High

A property name to write to depends on a
user-provided value
.
@yogeshchoudhary147 yogeshchoudhary147 force-pushed the feat/refresh-token-revocation branch 3 times, most recently from bdd24d8 to 0f91a7d Compare February 22, 2026 12:17
}

if (!response) {
if (abortController) abortController.abort();

Check warning

Code scanning / CodeQL

Client-side request forgery Medium

The
URL
of this request depends on a
user-provided value
.
@yogeshchoudhary147 yogeshchoudhary147 force-pushed the feat/refresh-token-revocation branch 2 times, most recently from 361af92 to 55cf7f6 Compare February 22, 2026 14:14
@yogeshchoudhary147 yogeshchoudhary147 force-pushed the feat/refresh-token-revocation branch from 55cf7f6 to c5df451 Compare February 25, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant