Skip to content

New doc: Kinde auth in Chrome extension#743

Open
tamalchowdhury wants to merge 5 commits into
mainfrom
tamal/new/kinde-chrome-extension
Open

New doc: Kinde auth in Chrome extension#743
tamalchowdhury wants to merge 5 commits into
mainfrom
tamal/new/kinde-chrome-extension

Conversation

@tamalchowdhury

@tamalchowdhury tamalchowdhury commented May 31, 2026

Copy link
Copy Markdown
Collaborator

Based on @DanielRivers's notes: this PR creates a new doc to integrate Kinde auth in a Chrome extension. The doc covers quick setup with PKCE auth, setup steps for development and production. It also covers using Kinde core features in the extension.

Summary by CodeRabbit

Summary by CodeRabbit

  • Documentation
    • Added a comprehensive developer guide for building and authenticating a Plasmo-based MV3 Chrome extension with Kinde.
    • Includes end-to-end examples for popup login/logout, background login with silent re-auth, dashboard token/session handling, sign-out, and access-token utilities.
    • Covers roles/permissions, multi-organization support, protected API call patterns, Management API guidance, production packaging/extension ID details, environment separation, and troubleshooting.

@tamalchowdhury
tamalchowdhury requested a review from a team as a code owner May 31, 2026 15:30
@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fc09ceee-13ab-4d43-9cc6-9757f123fcc4

📥 Commits

Reviewing files that changed from the base of the PR and between f611e75 and 2624971.

📒 Files selected for processing (1)
  • src/content/docs/developer-tools/guides/chrome-extension.mdx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/content/docs/developer-tools/guides/chrome-extension.mdx

Walkthrough

New MDX guide documenting Kinde PKCE authentication for a Plasmo MV3 Chrome extension, including setup, popup and dashboard UI, background token exchange and silent renewal, shared JWT/session helpers, styling, testing, production, and troubleshooting guidance.

Changes

Chrome Extension with Kinde PKCE Authentication

Layer / File(s) Summary
Setup & Project Scaffolding
src/content/docs/developer-tools/guides/chrome-extension.mdx
MDX frontmatter plus setup steps for Kinde app creation, Plasmo scaffolding, manifest permissions/host permissions, and required environment variables.
Popup UI Component
src/content/docs/developer-tools/guides/chrome-extension.mdx
Popup React UI that loads auth state, triggers background login/logout, shows status/errors, and opens the dashboard.
Background Worker: PKCE Auth & Token Exchange
src/content/docs/developer-tools/guides/chrome-extension.mdx
MV3 background worker example with message types, PKCE flow, token exchange, ChromeStore persistence, cleanup, sender validation, and silent renewal.
Dashboard UI & Session Helpers
src/content/docs/developer-tools/guides/chrome-extension.mdx
Dashboard page that reads session state, renders decoded profile fields, supports token reveal/copy and sign-out, plus dashboard-only session helpers.
JWT Utilities & Popup Auth Core
src/content/docs/developer-tools/guides/chrome-extension.mdx
JWT helpers and popup auth helpers for login, logout, shared storage, and silent refresh state lookup.
Design System, Styling & Build Patch
src/content/docs/developer-tools/guides/chrome-extension.mdx
Shared styles and the expo-secure-store postinstall stub used for Parcel bundling.
Testing, Roles, and Organizations
src/content/docs/developer-tools/guides/chrome-extension.mdx
Auth-flow testing steps plus roles/permissions and organization claim examples for UI gating and login routing.
Protected API, Feature Flags & Management API Guidance
src/content/docs/developer-tools/guides/chrome-extension.mdx
Bearer-token fetch helper guidance, JWKS validation notes, feature-flag/user-property claim examples, and backend-only Management API usage.
Production Packaging & Troubleshooting
src/content/docs/developer-tools/guides/chrome-extension.mdx
Production packaging guidance, stable extension ID notes, environment separation, and troubleshooting for auth, claims, CORS, and stub recovery.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Poem

I hopped through PKCE with a grin,
And tucked my tokens safely in.
The popup twitched, the dashboard sang,
While chrome and Kinde softly rang.
🐇✨ A happy guide, all neat within.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: a new documentation page about Kinde auth in a Chrome extension.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tamal/new/kinde-chrome-extension

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 31, 2026

Copy link
Copy Markdown

Deploying kinde-docs-preview with  Cloudflare Pages  Cloudflare Pages

Latest commit: 2624971
Status: ✅  Deploy successful!
Preview URL: https://6d549371.kinde-docs-preview.pages.dev
Branch Preview URL: https://tamal-new-kinde-chrome-exten.kinde-docs-preview.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/content/docs/developer-tools/guides/browser-extension.mdx (1)

111-114: 💤 Low value

Consider documenting or expanding regional host permissions.

The host_permissions include *.kinde.com and *.au.kinde.com, but Kinde has other regional domains (e.g., *.eu.kinde.com, *.us.kinde.com). Users in other regions may need to add their region's domain. Consider either:

  1. Adding a note explaining users should add their region's domain if applicable, or
  2. Including common regional patterns in the example.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/content/docs/developer-tools/guides/browser-extension.mdx` around lines
111 - 114, The host_permissions example only lists "https://*.kinde.com/*" and
"https://*.au.kinde.com/*", which may miss other regional domains; update the
browser extension docs around the host_permissions example to either add a short
note telling users to include their regional domain (e.g., *.eu.kinde.com,
*.us.kinde.com) or expand the example to include common regional patterns,
referencing the host_permissions array and the URL patterns shown so readers
know where to add their region-specific entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/content/docs/developer-tools/guides/browser-extension.mdx`:
- Around line 806-808: The doc claims lib/session.ts avoids importing
chrome.identity and keeping the dashboard bundle free of it, but signOut in
lib/session.ts directly calls chrome.identity.getRedirectURL and
chrome.identity.launchWebAuthFlow; either update the documentation to remove the
"keeps bundle free" claim or refactor signOut to mirror the login flow by
delegating the auth work to the background worker (e.g., add a bg message
handler and call chrome.runtime.sendMessage from signOut), remove direct
chrome.identity calls from signOut, and ensure the background handler performs
getRedirectURL/launchWebAuthFlow and returns the result to the dashboard caller.
- Around line 121-123: In the "8. Update environment variables" section update
the sentence that currently reads "add your Kinde credentials the **Details**
page" to include the missing preposition so it reads "add your Kinde credentials
from the **Details** page"; locate this text in
src/content/docs/developer-tools/guides/browser-extension.mdx within the "8.
Update environment variables" heading and insert "from" between "credentials"
and "the".
- Around line 95-97: The document has a step-numbering gap and an editorial
TODO: remove the stray "TODO: Review this section." and either renumber the "###
7. Update `package.json`" heading to the correct sequential step after "### 3.
Install Kinde dependency" (e.g., change to "### 4. Update `package.json`") or,
if actual steps 4–6 were intended, insert the missing steps content between "###
3. Install Kinde dependency" and the current update-package.json section; ensure
all headings (e.g., "### 3. Install Kinde dependency" and "### 7. Update
`package.json`") are sequential and consistent before publishing.
- Around line 1099-1128: Clarify the docs around the patch-expo-stub.mjs stub:
explain that `@kinde/js-utils`' ExpoSecureStore uses await
import("expo-secure-store") inside loadExpoStore() wrapped in a try/catch so the
import is lazy/optional, but because the script creates a real module under
node_modules/expo-secure-store the dynamic import will succeed (and the
try/catch won’t be exercised) — the stub only makes sense when you only use
ChromeStore and may still cause runtime errors if ExpoSecureStore is
instantiated outside Expo; also note that the script writes version "11.0.0"
which satisfies the peer range (>=11.0.0) and explicitly call out the fragility
of writing into node_modules (may require re-running after npm ci or tooling
changes).

---

Nitpick comments:
In `@src/content/docs/developer-tools/guides/browser-extension.mdx`:
- Around line 111-114: The host_permissions example only lists
"https://*.kinde.com/*" and "https://*.au.kinde.com/*", which may miss other
regional domains; update the browser extension docs around the host_permissions
example to either add a short note telling users to include their regional
domain (e.g., *.eu.kinde.com, *.us.kinde.com) or expand the example to include
common regional patterns, referencing the host_permissions array and the URL
patterns shown so readers know where to add their region-specific entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 207d279a-a3be-4904-906d-8f433069efd7

📥 Commits

Reviewing files that changed from the base of the PR and between b3f604c and 22ada2e.

📒 Files selected for processing (1)
  • src/content/docs/developer-tools/guides/browser-extension.mdx

Comment on lines +95 to +97
### 7. Update `package.json`

TODO: Review this section.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Step numbering gap and TODO comment need attention.

The section jumps from "### 3. Install Kinde dependency" directly to "### 7. Update package.json" — steps 4, 5, and 6 are missing. Additionally, line 97 contains a TODO: Review this section. comment that should be removed before publishing.

📝 Suggested fix
-### 7. Update `package.json`
-
-TODO: Review this section.
+### 4. Update `package.json`

Or add the missing steps 4-6 if they were intended to exist.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### 7. Update `package.json`
TODO: Review this section.
### 4. Update `package.json`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/content/docs/developer-tools/guides/browser-extension.mdx` around lines
95 - 97, The document has a step-numbering gap and an editorial TODO: remove the
stray "TODO: Review this section." and either renumber the "### 7. Update
`package.json`" heading to the correct sequential step after "### 3. Install
Kinde dependency" (e.g., change to "### 4. Update `package.json`") or, if actual
steps 4–6 were intended, insert the missing steps content between "### 3.
Install Kinde dependency" and the current update-package.json section; ensure
all headings (e.g., "### 3. Install Kinde dependency" and "### 7. Update
`package.json`") are sequential and consistent before publishing.

Comment on lines +121 to +123
### 8. Update environment variables

1. Create a `.env` file at the project root and add your Kinde credentials the **Details** page:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix grammar: missing preposition.

Line 123 reads "add your Kinde credentials the Details page" — the word "from" is missing.

📝 Suggested fix
-1. Create a `.env` file at the project root and add your Kinde credentials the **Details** page:
+1. Create a `.env` file at the project root and add your Kinde credentials from the **Details** page:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### 8. Update environment variables
1. Create a `.env` file at the project root and add your Kinde credentials the **Details** page:
### 8. Update environment variables
1. Create a `.env` file at the project root and add your Kinde credentials from the **Details** page:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/content/docs/developer-tools/guides/browser-extension.mdx` around lines
121 - 123, In the "8. Update environment variables" section update the sentence
that currently reads "add your Kinde credentials the **Details** page" to
include the missing preposition so it reads "add your Kinde credentials from the
**Details** page"; locate this text in
src/content/docs/developer-tools/guides/browser-extension.mdx within the "8.
Update environment variables" heading and insert "from" between "credentials"
and "the".

Comment thread src/content/docs/developer-tools/guides/chrome-extension.mdx Outdated
Comment thread src/content/docs/developer-tools/guides/chrome-extension.mdx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/content/docs/developer-tools/guides/chrome-extension.mdx (1)

110-113: 💤 Low value

Consider documenting additional Kinde regional domains.

The host_permissions only explicitly lists *.au.kinde.com/*. Kinde operates in multiple regions (US, EU, AU, etc.), and while *.kinde.com/* may cover most cases, users with custom domains or region-specific subdomains might need guidance.

Consider adding a note that users should include their specific custom domain if applicable (as done in the troubleshooting section at lines 1681-1688).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/content/docs/developer-tools/guides/chrome-extension.mdx` around lines
110 - 113, Update the manifest example's host_permissions entry to clarify
regional and custom domains: in the documentation around the "host_permissions"
example that currently shows "https://*.kinde.com/*" and
"https://*.au.kinde.com/*", add a short note telling users to include other
regional domains (e.g., EU/US subdomains) and any custom domains they use, and
reference the existing troubleshooting section that instructs adding custom
domains so readers know to add their own host_permissions when needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/content/docs/developer-tools/guides/chrome-extension.mdx`:
- Line 1175: Replace the misspelled word "devltools" with "devtools" in the
sentence "In the devltools, go to **Console** tab and run the following code:"
so the line reads "In the devtools, go to **Console** tab and run the following
code:"; update the string in the document content (the sentence containing
"devltools") to fix the typo.

---

Nitpick comments:
In `@src/content/docs/developer-tools/guides/chrome-extension.mdx`:
- Around line 110-113: Update the manifest example's host_permissions entry to
clarify regional and custom domains: in the documentation around the
"host_permissions" example that currently shows "https://*.kinde.com/*" and
"https://*.au.kinde.com/*", add a short note telling users to include other
regional domains (e.g., EU/US subdomains) and any custom domains they use, and
reference the existing troubleshooting section that instructs adding custom
domains so readers know to add their own host_permissions when needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 47fcef04-ecf6-47bc-ac5f-8ed656e2d0d5

📥 Commits

Reviewing files that changed from the base of the PR and between 22ada2e and 4683ae0.

📒 Files selected for processing (1)
  • src/content/docs/developer-tools/guides/chrome-extension.mdx

Comment thread src/content/docs/developer-tools/guides/chrome-extension.mdx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/content/docs/developer-tools/guides/chrome-extension.mdx (5)

118-138: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the exact Kinde host in host_permissions.

The setup path already allows a custom domain, but this manifest example only whitelists *.kinde.com / *.au.kinde.com. Readers who set PLASMO_PUBLIC_KINDE_DOMAIN to a custom host will copy this config and hit token-exchange/CORS failures until they reach troubleshooting, and the wildcard is broader than the extension needs. Document this here as “use the same exact Kinde domain you put in .env.”

Suggested doc tweak
-      "host_permissions": [
-        "https://*.kinde.com/*",
-        "https://*.au.kinde.com/*"
-      ]
+      "host_permissions": [
+        "https://your-kinde-domain.example/*"
+      ]
-The `host_permissions` are required so the background service worker can make cross-origin requests to Kinde's token endpoint without being blocked by CORS.
+The `host_permissions` entry must match the exact Kinde domain your extension uses in `.env` (including a custom domain, if you have one). This lets the background service worker call Kinde's token endpoint without CORS failures.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/content/docs/developer-tools/guides/chrome-extension.mdx` around lines
118 - 138, Update the manifest example in the package.json snippet so the
host_permissions entry uses the exact Kinde domain that readers set in their
environment (the PLASMO_PUBLIC_KINDE_DOMAIN value) rather than broad wildcards;
specifically, change the explanatory text around the "manifest" /
"host_permissions" example to instruct readers to copy the exact host they
configured in their .env (e.g., use the exact Kinde host used by
PLASMO_PUBLIC_KINDE_DOMAIN) to avoid CORS token-exchange failures and to
remove/avoid overly permissive wildcards like "*.kinde.com".

1483-1490: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Derive JWKS from the configured issuer, not a fixed .kinde.com host.

The rest of the guide supports AU domains and custom domains, but this section tells readers to validate against https://<your-subdomain>.kinde.com/.well-known/jwks. That points those deployments at the wrong key set. Phrase it as “append /.well-known/jwks to the same Kinde domain/issuer you configured for the extension.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/content/docs/developer-tools/guides/chrome-extension.mdx` around lines
1483 - 1490, Replace the hardcoded example URL
"https://<your-subdomain>.kinde.com/.well-known/jwks" with guidance to derive
the JWKS URL from the configured Kinde issuer/domain for the extension; instruct
readers to append "/.well-known/jwks" to the same issuer/domain they configured
(rather than assuming ".kinde.com") and update the surrounding sentence to
mention support for AU and custom domains and using a JWKS-capable JWT library
(e.g., "jose") to verify signature, expiry, and audience.

726-736: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore = padding before calling atob in decodeJwt

decodeJwt() converts base64url → base64 but never re-adds the = padding that atob expects. Since JWT base64url omits trailing = characters, decoding can throw (or fail) for valid tokens whose segment length isn’t a multiple of 4.

Suggested fix
 export function decodeJwt(token: string): Record<string, unknown> {
   try {
     const base64Url = token.split(".")[1]
-    const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/")
+    const base64 = base64Url
+      .replace(/-/g, "+")
+      .replace(/_/g, "/")
+      .padEnd(Math.ceil(base64Url.length / 4) * 4, "=")
     const jsonPayload = decodeURIComponent(
       atob(base64)
         .split("")
         .map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2))
         .join(""),
     )
     return JSON.parse(jsonPayload)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/content/docs/developer-tools/guides/chrome-extension.mdx` around lines
726 - 736, The decodeJwt function is missing re-addition of '=' padding before
calling atob; update the logic in decodeJwt (using the base64 variable derived
from base64Url) to compute required padding ((4 - (base64.length % 4)) % 4) and
append that many '=' characters to base64 prior to calling atob so base64 length
is a multiple of 4, then proceed to build jsonPayload and JSON.parse as before;
modify the code around the base64 and atob usage in decodeJwt to insert this
padding step.

1455-1470: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize options.headers with Headers before setting Authorization.

fetch RequestInit.headers accepts HeadersInit (a Headers instance, a plain object, or an array of [name, value] tuples). Spreading options.headers into an object literal can silently drop/garble the caller-provided values (e.g., spreading a Headers instance yields no enumerable header entries; spreading tuple arrays produces numeric keys like {"0": [...]}), so this helper may send incorrect headers.

Suggested fix
 export async function fetchWithAuth(
   url: string,
   options: RequestInit = {},
 ): Promise<Response> {
   const state = await getAuthState()
   if (!state.isAuthenticated || !state.accessToken) {
     throw new Error("Not authenticated")
   }
 
+  const headers = new Headers(options.headers)
+  headers.set("Authorization", `Bearer ${state.accessToken}`)
+
   return fetch(url, {
     ...options,
-    headers: {
-      ...options.headers,
-      Authorization: `Bearer ${state.accessToken}`,
-    },
+    headers,
   })
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/content/docs/developer-tools/guides/chrome-extension.mdx` around lines
1455 - 1470, The fetchWithAuth helper currently spreads options.headers into an
object which can drop or corrupt headers when callers pass a Headers instance or
header tuples; change fetchWithAuth to normalize headers by creating a new
Headers from options.headers (e.g., new Headers(options.headers)), call
headers.set("Authorization", `Bearer ${state.accessToken}`) on that Headers
instance, and pass that Headers instance as the headers property in the fetch
call so Authorization is added reliably without mutating the caller's input;
reference the fetchWithAuth function and getAuthState to locate where to apply
this change.

799-807: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix login() error handling: don’t swallow all chrome.runtime.sendMessage failures

  • In src/content/docs/developer-tools/guides/chrome-extension.mdx the catch { return } in login() (and the same pattern in the login(orgCode?: string) overload) suppresses every message error, masking real background/listener failures instead of only the expected macOS popup-close/port-closed scenario.
  • Re-throw everything except the specific “message port/channel closed before response” case.
Suggested fix
-  } catch {
-    // Popup closed before background responded (expected on macOS).
-    return
+  } catch (err) {
+    const message = err instanceof Error ? err.message : String(err)
+    if (
+      message.includes("message port closed") ||
+      message.includes("message channel closed")
+    ) {
+      // Popup closed before background responded (expected on macOS).
+      return
+    }
+    throw err
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/content/docs/developer-tools/guides/chrome-extension.mdx` around lines
799 - 807, The catch in login() (and its overload login(orgCode?: string))
currently swallows all errors from chrome.runtime.sendMessage; change it to only
ignore the specific "message port/channel closed before response" error and
re-throw any other failures. Concretely, catch the thrown error as e, inspect
e.message (or e.toString()) for the known Chrome message like "The message port
closed before a response was received" or "Could not establish connection.
Receiving end does not exist.", return silently only for those cases, and
otherwise re-throw the error so real background/listener failures surface.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/content/docs/developer-tools/guides/chrome-extension.mdx`:
- Around line 118-138: Update the manifest example in the package.json snippet
so the host_permissions entry uses the exact Kinde domain that readers set in
their environment (the PLASMO_PUBLIC_KINDE_DOMAIN value) rather than broad
wildcards; specifically, change the explanatory text around the "manifest" /
"host_permissions" example to instruct readers to copy the exact host they
configured in their .env (e.g., use the exact Kinde host used by
PLASMO_PUBLIC_KINDE_DOMAIN) to avoid CORS token-exchange failures and to
remove/avoid overly permissive wildcards like "*.kinde.com".
- Around line 1483-1490: Replace the hardcoded example URL
"https://<your-subdomain>.kinde.com/.well-known/jwks" with guidance to derive
the JWKS URL from the configured Kinde issuer/domain for the extension; instruct
readers to append "/.well-known/jwks" to the same issuer/domain they configured
(rather than assuming ".kinde.com") and update the surrounding sentence to
mention support for AU and custom domains and using a JWKS-capable JWT library
(e.g., "jose") to verify signature, expiry, and audience.
- Around line 726-736: The decodeJwt function is missing re-addition of '='
padding before calling atob; update the logic in decodeJwt (using the base64
variable derived from base64Url) to compute required padding ((4 -
(base64.length % 4)) % 4) and append that many '=' characters to base64 prior to
calling atob so base64 length is a multiple of 4, then proceed to build
jsonPayload and JSON.parse as before; modify the code around the base64 and atob
usage in decodeJwt to insert this padding step.
- Around line 1455-1470: The fetchWithAuth helper currently spreads
options.headers into an object which can drop or corrupt headers when callers
pass a Headers instance or header tuples; change fetchWithAuth to normalize
headers by creating a new Headers from options.headers (e.g., new
Headers(options.headers)), call headers.set("Authorization", `Bearer
${state.accessToken}`) on that Headers instance, and pass that Headers instance
as the headers property in the fetch call so Authorization is added reliably
without mutating the caller's input; reference the fetchWithAuth function and
getAuthState to locate where to apply this change.
- Around line 799-807: The catch in login() (and its overload login(orgCode?:
string)) currently swallows all errors from chrome.runtime.sendMessage; change
it to only ignore the specific "message port/channel closed before response"
error and re-throw any other failures. Concretely, catch the thrown error as e,
inspect e.message (or e.toString()) for the known Chrome message like "The
message port closed before a response was received" or "Could not establish
connection. Receiving end does not exist.", return silently only for those
cases, and otherwise re-throw the error so real background/listener failures
surface.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a4cc5cb9-435c-4844-bd8b-b875cdea285f

📥 Commits

Reviewing files that changed from the base of the PR and between 4683ae0 and 2b044ee.

📒 Files selected for processing (1)
  • src/content/docs/developer-tools/guides/chrome-extension.mdx

@tamalchowdhury tamalchowdhury changed the title 🚧 Work in Progress: New doc: Kinde auth in Chrome extension New doc: Kinde auth in Chrome extension Jun 9, 2026

@shafaladhikari shafaladhikari left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work. Please have a look at the comments where few changes can be done for better docs.

},
"manifest": {
"permissions": ["identity", "storage"],
"host_permissions": [

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the setup example use the exact Kinde domain from .env rather than *.kinde.com wildcards? Custom-domain users will hit CORS until they reach troubleshooting otherwise.


```ts
// lib/jwt.ts
export function decodeJwt(token: string): Record<string, unknown> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sample decodeJwt is missing base64 padding before atob(). Some valid JWTs will fail to decode — worth adding .padEnd(...).


### `lib/kinde.ts`

Core auth helpers used by the popup. `login()` delegates the interactive OAuth flow to the background via `KINDE_LOGIN`. `getAuthState()` automatically attempts a silent re-authentication via `KINDE_SILENT_AUTH` when the stored token is expired — if the user's Kinde browser session is still alive, fresh tokens are returned with no user prompt. If the session has also expired, the user is prompted to sign in again.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KINDE_SILENT_AUTH re-runs the authorization code flow with interactive: false — it's session-based silent re-auth, not a refresh-token grant. Refresh tokens are stored after exchangeAuthCode but never read for renewal. Consider renaming "silent token renewal" to "silent re-authentication" in the doc and noting that it only works while the Kinde SSO session is still active.

response = await chrome.runtime.sendMessage<LoginMessage, LoginResponse>({
type: "KINDE_LOGIN",
})
} catch {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The login() sample uses a bare catch { return }, which silently ignores every chrome.runtime.sendMessage failure — not just the expected macOS popup-close case.

On macOS, when the OAuth window opens, Chrome often tears down the popup before the background worker responds. That produces errors like:

"The message port closed before a response was received."
"Could not establish connection. Receiving end does not exist."
Those are expected and safe to ignore. But other failures — a missing background listener, a service worker that failed to start, a misconfigured manifest — get swallowed too, so developers copying this code won't see real bugs during setup.

Suggest narrowing the catch to only those known popup-lifecycle errors and re-throwing everything else:

} catch (err) {
  const message = err instanceof Error ? err.message : String(err)
  if (
    message.includes("message port closed") ||
    message.includes("message channel closed") ||
    message.includes("Receiving end does not exist")
  ) {
    // Popup closed before background responded (expected on macOS).
    return
  }
  throw err
}

The same pattern appears in the Organizations section (login(orgCode?) around line 1430) — worth applying there too for consistency.


return fetch(url, {
...options,
headers: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fetchWithAuth helper spreads options.headers into a plain object:

headers: {
  ...options.headers,
  Authorization: `Bearer ${state.accessToken}`,
}

That works when callers pass a simple object like { "Content-Type": "application/json" }, but fetch's headers option accepts HeadersInit — which can also be a Headers instance or an array of [name, value] tuples.

Spreading a Headers object yields no enumerable entries, so caller-provided headers are dropped. Spreading a tuple array produces numeric keys ({ "0": ["Content-Type", "application/json"] }), which fetch won't interpret correctly. In both cases, Authorization may be the only header actually sent.

Since this is copy-paste sample code, it's worth normalizing headers with the Headers constructor before setting Authorization:

export async function fetchWithAuth(
  url: string,
  options: RequestInit = {},
): Promise<Response> {
  const state = await getAuthState()
  if (!state.isAuthenticated || !state.accessToken) {
    throw new Error("Not authenticated")
  }
  const headers = new Headers(options.headers)
  headers.set("Authorization", `Bearer ${state.accessToken}`)
  return fetch(url, {
    ...options,
    headers,
  })
}

This handles plain objects, Headers instances, and tuple arrays reliably, and avoids mutating the caller's input.

@tamalchowdhury

Copy link
Copy Markdown
Collaborator Author

Hi @shafaladhikari thanks for this detailed and helpful review. learned a lot. I made some improvements; kindly have a look.

@onderay
onderay requested a review from shafaladhikari July 8, 2026 06:05

@shafaladhikari shafaladhikari left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great. Everything has been addressed and looks good to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants