Skip to content

fix(shared): correct isValidBrowser/isBrowserOnline for React Native#8086

Closed
chriscanin wants to merge 1 commit intomainfrom
chris/fix-gettoken-template-offline-rn
Closed

fix(shared): correct isValidBrowser/isBrowserOnline for React Native#8086
chriscanin wants to merge 1 commit intomainfrom
chris/fix-gettoken-template-offline-rn

Conversation

@chriscanin
Copy link
Member

@chriscanin chriscanin commented Mar 16, 2026

Summary

  • isValidBrowser() and isBrowserOnline() in @clerk/shared were returning false when window/navigator are undefined (React Native environments)
  • Core-3's token fetching checks these functions before making FAPI calls — returning false caused getToken({ template: '...' }) to silently fail on React Native while getToken() (which reads from local cache) continued to work
  • Fix: return true in non-browser environments since React Native has its own networking layer and should not be treated as a bot or offline

Reported by

Bledar via Discord — getToken({ template: 'one_hour_token' }) returns nothing on Expo after upgrading to latest Clerk

Test plan

  • Updated existing browser.spec.ts test to verify isValidBrowser() returns true when window is undefined
  • All 983 tests in @clerk/shared pass
  • Verify getToken({ template }) works on React Native after fix

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed token retrieval with templates in React Native and other non-browser environments. Non-browser runtimes are now correctly recognized as valid execution contexts, ensuring template-based token operations function properly across React Native, server-side rendering, and similar platforms. This resolves compatibility issues that previously prevented proper authentication token management in these environments.

…owser environments

In React Native, there is no `window` or `navigator`, so `isValidBrowser()` and
`isBrowserOnline()` were returning `false`. This caused core-3's token fetching
to skip FAPI calls for templated tokens (`getToken({ template })`) since it
believed the environment was invalid/offline.

Non-browser runtimes like React Native have their own networking layer and should
not be treated as bots or offline. Return `true` when browser APIs are absent.
@vercel
Copy link

vercel bot commented Mar 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Mar 16, 2026 4:38pm

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Mar 16, 2026

🦋 Changeset detected

Latest commit: c3e2e81

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 21 packages
Name Type
@clerk/shared Patch
@clerk/agent-toolkit Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/clerk-js Patch
@clerk/expo-passkeys Patch
@clerk/expo Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/hono Patch
@clerk/localizations Patch
@clerk/msw Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/react Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/ui Patch
@clerk/vue Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 16, 2026

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@8086

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8086

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8086

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8086

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8086

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8086

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8086

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8086

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8086

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8086

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8086

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8086

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8086

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8086

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8086

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8086

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8086

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8086

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8086

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8086

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8086

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8086

commit: c3e2e81

@chriscanin chriscanin self-assigned this Mar 16, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 16, 2026

📝 Walkthrough

Walkthrough

This change modifies the behavior of browser detection functions in the shared package to support token retrieval in React Native environments. The isValidBrowser() and isBrowserOnline() functions in browser.ts are updated to return true when running in non-browser environments instead of false. Corresponding test expectations are updated to reflect this new behavior. A changeset file documents this as a patch release for "@clerk/shared".

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and specifically describes the main change: fixing isValidBrowser and isBrowserOnline behavior for React Native environments.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/shared/src/browser.ts`:
- Around line 60-63: The functions isValidBrowser() and isBrowserOnline() should
not return true for all non-navigator runtimes; narrow the "true" case to only
React Native. Update isValidBrowser() and isBrowserOnline() to detect React
Native explicitly (e.g., check navigator?.product === 'ReactNative' or another
RN-specific signal) and return true only in that case, otherwise return false
for generic non-browser runtimes so code like the guard in webauthn.ts
("isValidBrowser() && typeof window.PublicKeyCredential === 'function'") does
not attempt to access window in SSR/Node environments; ensure the detection
logic is used in the existing functions named isValidBrowser and
isBrowserOnline.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6d624320-01f8-40cd-9142-3372eccc1f4f

📥 Commits

Reviewing files that changed from the base of the PR and between da2b239 and c3e2e81.

📒 Files selected for processing (3)
  • .changeset/fix-gettoken-template-rn.md
  • packages/shared/src/__tests__/browser.spec.ts
  • packages/shared/src/browser.ts

Comment on lines +60 to +63
// Not in a browser environment (e.g. React Native, SSR).
// Return true so non-browser runtimes are not incorrectly
// treated as bots or automated browsers.
return true;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Find call sites that use isValidBrowser() as a browser/window guard:"
rg -nP --type=ts --type=tsx -C3 'isValidBrowser\(\)\s*&&\s*(typeof\s+window\.|window\.)'

echo
echo "2) Inspect key downstream paths impacted by non-browser=true behavior:"
rg -nP -C3 '\bisBrowserOnline\(\)|\bisValidBrowserOnline\(\)' \
  packages/clerk-js/src/core/resources/Session.ts \
  packages/clerk-js/src/core/fapiClient.ts \
  packages/shared/src/webauthn.ts

echo
echo "Expected:"
echo "- If any server-side/SSR path relies on isValidBrowser() to guard window access,"
echo "  current change is unsafe and should be narrowed to RN detection."

Repository: clerk/javascript

Length of output: 176


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Step 1: Check actual code in browser.ts around lines 60-63 and 76-79 ==="
cat -n packages/shared/src/browser.ts | sed -n '50,85p'

echo
echo "=== Step 2: Find all imports and usages of isValidBrowser ==="
rg -n 'isValidBrowser' packages/ --max-count=20

echo
echo "=== Step 3: Find all imports and usages of isBrowserOnline ==="
rg -n 'isBrowserOnline' packages/ --max-count=20

echo
echo "=== Step 4: Check webauthn.ts guard pattern ==="
cat -n packages/shared/src/webauthn.ts | head -20

Repository: clerk/javascript

Length of output: 7354


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Test expectations for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,75p'

echo
echo "=== Check if webauthn.ts has any build-time guards or SSR exclusion ==="
grep -r "webauthn" packages/ --include="*.ts" --include="*.tsx" --include="*.json" | grep -E "(exclude|server|ssr|node)" || echo "No SSR-specific guards found"

echo
echo "=== Check for React Native detection utility in codebase ==="
rg -n "ReactNative|react.native|react-native" packages/shared/src/ | head -20

echo
echo "=== Check inBrowser() implementation ==="
cat -n packages/shared/src/browser.ts | sed -n '1,50p'

Repository: clerk/javascript

Length of output: 3163


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Where is isWebAuthnSupported() actually called? ==="
rg -rn "isWebAuthnSupported|webauthn" packages/ --type=ts --type=tsx --include="*.ts" --include="*.tsx" | grep -v "node_modules" | grep -v "test" | head -30

echo
echo "=== Check if webauthn module is conditionally loaded ==="
rg -rn "from.*webauthn|import.*webauthn" packages/ --include="*.ts" --include="*.tsx" | grep -v test

echo
echo "=== Full webauthn.ts to understand usage pattern ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 574


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Where is isWebAuthnSupported() or webauthn functions called? ==="
rg -n "isWebAuthnSupported|isWebAuthnAutofillSupported" packages/ 

echo
echo "=== Check all webauthn imports ==="
rg -n "from.*webauthn|import.*webauthn" packages/

echo
echo "=== Full webauthn.ts content ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 8269


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check if webauthn.ts is server-side accessible or client-only ==="
rg -n "webauthn" packages/shared/package.json packages/shared/tsconfig.json 2>/dev/null || echo "Not in explicit exports"

echo
echo "=== Check how webauthn.ts is exported from `@clerk/shared` ==="
cat packages/shared/package.json | grep -A 20 '"exports"'

echo
echo "=== Look at complete test for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '41,47p'

echo
echo "=== Does the test actually expect this behavior? Look at comment ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,50p'

echo
echo "=== Check if there's conditional execution or build-time webauthn exclusion ==="
grep -r "webauthn" packages/clerk-js/src --include="*.json" --include="*.config.*" 2>/dev/null || echo "No special build config for webauthn"

echo
echo "=== Key: check if window.PublicKeyCredential will actually throw in SSR ==="
echo "Testing: in Node.js SSR, accessing window.PublicKeyCredential"
node -e "try { console.log(typeof window.PublicKeyCredential); } catch(e) { console.log('Error:', e.message); }"

Repository: clerk/javascript

Length of output: 2066


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Final verification: Can isWebAuthnSupported ever run in SSR? ==="
echo "Check if SignIn/Session resources are server-side initialized:"
rg -n "new SignIn\|new Session\|__internal_isWebAuthnSupported" packages/clerk-js/src/core/clerk.ts | head -15

echo
echo "=== Confirm: in SSR with new isValidBrowser() change, this will crash ==="
echo "Simulating: inBrowser()=false, navigator=null, then evaluating typeof window.PublicKeyCredential"
cat > /tmp/test.js << 'EOF'
// Simulate isValidBrowser() with new change
function isValidBrowser_NEW() {
  // In SSR: inBrowser() is false, so navigator becomes null
  const navigator = false ? null : null;  // inBrowser() returns false
  if (!navigator) {
    return true;  // NEW BEHAVIOR: return true for non-browser
  }
  return false;
}

// Now the guard in isWebAuthnSupported
function isWebAuthnSupported() {
  return (
    isValidBrowser_NEW() &&
    typeof window.PublicKeyCredential === 'function'  // <-- will crash
  );
}

try {
  console.log("Result:", isWebAuthnSupported());
} catch (e) {
  console.log("CRASH:", e.message);
}
EOF
node /tmp/test.js

Repository: clerk/javascript

Length of output: 191


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check if clerk resources are instantiated in SSR initialization ==="
rg -n "initializeClerk|new SignIn|new Session" packages/clerk-js/src/core/clerk.ts | head -5

echo
echo "=== Confirm window check in isValidBrowser matches inBrowser pattern ==="
cat -n packages/shared/src/browser.ts | sed -n '57,66p'

Repository: clerk/javascript

Length of output: 131


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Verify: is webauthn always guarded with typeof checks on import side? ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '70,80p'

echo
echo "=== Check full context of how isWebAuthnSupported is used in SignIn ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '514,525p'

echo
echo "=== Simplified: In SSR, would isWebAuthnSupported() be called? ==="
echo "Check if SignIn methods are accessible in SSR or only browser-side"
grep -n "class SignIn" packages/clerk-js/src/core/resources/SignIn.ts | head -1
</script>

Repository: clerk/javascript

Length of output: 1639


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check: When are webauthn functions evaluated? At load time or at runtime? ==="
cat -n packages/shared/src/webauthn.ts

echo
echo "=== Check: Are these webauthn functions called during SSR server init? ==="
echo "They're exported from `@clerk/shared` and imported in clerk-js"
echo "But are they called during non-interactive SSR, or only during client-side flows?"

echo
echo "=== The critical question: Does the guard actually protect? ==="
echo "In webauthn.ts line 8:"
echo "  isValidBrowser() && typeof window.PublicKeyCredential === 'function'"
echo ""
echo "With new isValidBrowser() = true in SSR:"
echo "  true && typeof window.PublicKeyCredential === 'function'"
echo "  ^   This evaluates to the right side"
echo "  ^   But we're in Node.js where window is undefined"
echo "  -> ReferenceError: window is not defined"

Repository: clerk/javascript

Length of output: 1880


isValidBrowser() and isBrowserOnline() returning true for all non-navigator cases breaks webauthn guard semantics

Lines 60 and 76 returning true for all non-browser runtimes (not just React Native) creates a safety issue. In packages/shared/src/webauthn.ts:8–10, the guard pattern isValidBrowser() && typeof window.PublicKeyCredential === 'function' relies on the first condition being false in non-browser environments. With this change, SSR/Node.js environments will pass the first condition (true), then evaluate typeof window.PublicKeyCredential, which throws ReferenceError: window is not defined since typeof doesn't protect property access.

The test comment correctly identifies the intent ("React Native"), but the implementation conflates React Native with generic non-browser runtimes. Narrow the true return to React Native detection only:

+function isReactNativeRuntime(): boolean {
+  return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
+}
+
 export function isValidBrowser(): boolean {
   const navigator = inBrowser() ? window?.navigator : null;
   if (!navigator) {
-    return true;
+    return isReactNativeRuntime();
   }
   return !userAgentIsRobot(navigator?.userAgent) && !navigator?.webdriver;
 }

 export function isBrowserOnline(): boolean {
   const navigator = inBrowser() ? window?.navigator : null;
   if (!navigator) {
-    return true;
+    return isReactNativeRuntime();
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/browser.ts` around lines 60 - 63, The functions
isValidBrowser() and isBrowserOnline() should not return true for all
non-navigator runtimes; narrow the "true" case to only React Native. Update
isValidBrowser() and isBrowserOnline() to detect React Native explicitly (e.g.,
check navigator?.product === 'ReactNative' or another RN-specific signal) and
return true only in that case, otherwise return false for generic non-browser
runtimes so code like the guard in webauthn.ts ("isValidBrowser() && typeof
window.PublicKeyCredential === 'function'") does not attempt to access window in
SSR/Node environments; ensure the detection logic is used in the existing
functions named isValidBrowser and isBrowserOnline.

@chriscanin
Copy link
Member Author

This PR is being closed in favor of #8084.

@chriscanin chriscanin closed this Mar 16, 2026
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