Skip to content

Fix extension stuck on login screen after sign-in#264

Merged
bd73-com merged 4 commits intomainfrom
claude/fix-extension-login-issue-wcRSZ
Mar 25, 2026
Merged

Fix extension stuck on login screen after sign-in#264
bd73-com merged 4 commits intomainfrom
claude/fix-extension-login-issue-wcRSZ

Conversation

@bd73-com
Copy link
Owner

@bd73-com bd73-com commented Mar 25, 2026

Summary

  • Root cause: After OAuth login from the extension auth page, the callback always redirected to / instead of back to /extension-auth. The content script never ran post-login, so the JWT token was never relayed to the extension — leaving it stuck on the "Connect your account" screen.
  • Fix: Store a returnTo query parameter in the session during /api/login and use it in the OAuth callback redirect. The ExtensionAuth page now passes returnTo=/extension-auth when linking to login.
  • Security hardening: Extracted sanitizeReturnTo() with defense-in-depth against open redirects (protocol-relative, absolute URLs), CRLF injection (literal + percent-encoded), null bytes, all control characters (\x00-\x1f), and unbounded input length (2048 cap). 18 test cases.
  • Session reliability: Explicit req.session.save() before OAuth redirect ensures returnTo persists to the session store across the identity provider round-trip.

Test plan

  • Install extension, click "Connect your account" while logged out
  • Verify you land back on /extension-auth after OAuth (not /)
  • Verify the extension popup shows authenticated state after sign-in
  • Verify normal login (not from extension) still redirects to /
  • Verify returnTo with absolute URLs (e.g. https://evil.com) is rejected
  • Verify returnTo with protocol-relative URLs (e.g. //evil.com) is rejected
  • Verify returnTo with CRLF injection attempts is rejected
  • Verify returnTo longer than 2048 characters is rejected

https://claude.ai/code/session_011668oYNRLot8LEzJF1wqjA

Summary by CodeRabbit

  • Bug Fixes
    • Fixed the login flow to preserve and correctly redirect users back to the extension authentication page after successful login, improving the user experience during extension setup.

After clicking "Sign in" on the extension auth page, the OAuth callback
always redirected to "/" instead of back to /extension-auth. This meant
the content script never ran on the auth page post-login, so the JWT
token was never relayed to the extension — leaving it stuck on the
login screen.

Store a returnTo parameter in the session during /api/login and use it
in the OAuth callback redirect. Only relative paths are accepted to
prevent open-redirect attacks.

https://claude.ai/code/session_011668oYNRLot8LEzJF1wqjA
@github-actions github-actions bot added the fix label Mar 25, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 25, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Client adds a returnTo query to the login link; server validates and stores the sanitized value in session before starting OIDC auth, and on callback restores+deletes it to redirect the user (fallback /). Security note: review path normalization/encoding handling for bypass risks.

Changes

Cohort / File(s) Summary
Client-side login link
client/src/pages/ExtensionAuth.tsx
Updated unauthenticated login URL to include ?returnTo=/extension-auth so post-login destination can be preserved.
Server-side auth flow & helper
server/replit_integrations/auth/replitAuth.ts
Added exported sanitizeReturnTo(value) to validate relative returnTo paths; store sanitized returnTo in req.session on GET /api/login, explicitly save session before redirecting to OIDC, and on GET /api/callback retrieve+delete it to redirect to `returnTo
Unit tests
server/replit_integrations/auth/replitAuth.test.ts
New vitest suite for sanitizeReturnTo, covering valid relative paths and rejecting protocol/absolute URLs, javascript: URIs, CRLF/null/tab injections (literal and percent-encoded), backslashes, overlong values, and non-string inputs.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant AppServer
    participant SessionStore
    participant OIDCProvider

    Client->>AppServer: GET /api/login?returnTo=/extension-auth
    AppServer->>AppServer: sanitizeReturnTo(req.query.returnTo)
    AppServer->>SessionStore: store session.returnTo (and save)
    AppServer->>OIDCProvider: redirect to OIDC auth (Passport)
    OIDCProvider-->>Client: OIDC consent -> redirect to /api/callback
    Client->>AppServer: GET /api/callback (auth code)
    AppServer->>OIDCProvider: exchange code, verify user
    AppServer->>SessionStore: persist authenticated session
    AppServer->>SessionStore: read & delete session.returnTo
    AppServer-->>Client: redirect to session.returnTo || /
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: addressing the extension getting stuck on the login screen after sign-in by implementing redirect logic to return users to the extension auth page post-login.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/fix-extension-login-issue-wcRSZ

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.

Copy link

@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

Caution

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

⚠️ Outside diff range comments (2)
client/src/pages/ExtensionAuth.tsx (2)

38-38: ⚠️ Potential issue | 🟡 Minor

Missing error in useEffect dependency array.

The effect checks error in its guard condition (line 12) but error is not included in the dependency array. If error is set and then cleared externally, the effect won't re-run to retry the token fetch.

🛠️ Proposed fix
-  }, [user, tokenSent]);
+  }, [user, tokenSent, error]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/pages/ExtensionAuth.tsx` at line 38, The useEffect in
ExtensionAuth.tsx references the `error` variable in its guard but the
dependency array only contains `[user, tokenSent]`; update the effect's
dependency array to include `error` (i.e., `[user, tokenSent, error]`) so the
effect re-runs when error changes and retries the token fetch logic (the effect
that checks `error`, `user`, and `tokenSent` and invokes the token
fetch/handler).

61-61: 🧹 Nitpick | 🔵 Trivial

Use semantic color token instead of hardcoded emerald.

Per coding guidelines, use semantic Tailwind color tokens. Consider using text-success or similar semantic token if defined, or text-primary for consistency.

♻️ Proposed change
-            <CheckCircle2 className="h-12 w-12 text-emerald-500 dark:text-emerald-400 mx-auto" />
+            <CheckCircle2 className="h-12 w-12 text-primary mx-auto" />

As per coding guidelines: "Use semantic Tailwind color tokens (text-primary, text-muted-foreground, bg-secondary, bg-background, border-border, etc.) instead of hardcoded hex, RGB, or HSL values in JSX or class names"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/pages/ExtensionAuth.tsx` at line 61, The CheckCircle2 icon in
ExtensionAuth is using hardcoded color classes (text-emerald-500 and
dark:text-emerald-400); update its className to use the project's semantic
Tailwind tokens (for example replace those with text-success and
dark:text-success-foreground or text-primary and dark:text-primary-foreground
depending on the token defined) so the icon follows semantic theming; modify the
className on the CheckCircle2 element accordingly (keeping other utility classes
like sizing and mx-auto intact).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/replit_integrations/auth/replitAuth.ts`:
- Around line 118-122: The returnTo assignment allows bypass via backslashes
(e.g. "/\\evil.com" or "/%5Cevil.com"); add a validation helper (e.g.
isValidReturnTo) and use it when setting returnTo to ensure the value is a
string that starts with "/" but not "//", contains no backslash characters (\"),
and contains no percent-encoded backslashes ("%5C" or "%5c"); optionally
decodeURIComponent first and/or validate with URL parsing against a fixed base
to ensure the path stays local. Apply this helper where returnTo is computed to
replace the current inline checks.

---

Outside diff comments:
In `@client/src/pages/ExtensionAuth.tsx`:
- Line 38: The useEffect in ExtensionAuth.tsx references the `error` variable in
its guard but the dependency array only contains `[user, tokenSent]`; update the
effect's dependency array to include `error` (i.e., `[user, tokenSent, error]`)
so the effect re-runs when error changes and retries the token fetch logic (the
effect that checks `error`, `user`, and `tokenSent` and invokes the token
fetch/handler).
- Line 61: The CheckCircle2 icon in ExtensionAuth is using hardcoded color
classes (text-emerald-500 and dark:text-emerald-400); update its className to
use the project's semantic Tailwind tokens (for example replace those with
text-success and dark:text-success-foreground or text-primary and
dark:text-primary-foreground depending on the token defined) so the icon follows
semantic theming; modify the className on the CheckCircle2 element accordingly
(keeping other utility classes like sizing and mx-auto intact).
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0df0ed01-a784-4055-be2c-454d8ca2db5f

📥 Commits

Reviewing files that changed from the base of the PR and between 4af0e7b and f039544.

📒 Files selected for processing (2)
  • client/src/pages/ExtensionAuth.tsx
  • server/replit_integrations/auth/replitAuth.ts

claude added 2 commits March 25, 2026 13:18
Extract sanitizeReturnTo() as a testable pure function with defense-
in-depth against CRLF injection (literal and percent-encoded) and
unbounded input length. Add 16 test cases covering open redirect,
CRLF, length limits, and type coercion edge cases.

https://claude.ai/code/session_011668oYNRLot8LEzJF1wqjA
- Broaden control character rejection from \r\n to all \x00-\x1f
  (including null bytes and tabs) and their percent-encoded forms
- Add explicit req.session.save() before OAuth redirect to ensure
  returnTo persists to the session store across the identity provider
  round-trip
- Add test cases for null byte and tab character injection

https://claude.ai/code/session_011668oYNRLot8LEzJF1wqjA
Copy link

@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: 2

♻️ Duplicate comments (1)
server/replit_integrations/auth/replitAuth.ts (1)

16-23: ⚠️ Potential issue | 🔴 Critical

Critical open-redirect bypass remains via backslash variants.

sanitizeReturnTo still allows payloads like /\evil.com (and encoded %5C variants), which can be normalized into protocol-relative redirects by some clients. This keeps an open-redirect path alive.

🔒 Suggested hardening
 export function sanitizeReturnTo(value: unknown): string | undefined {
   if (typeof value !== "string") return undefined;
   if (value.length > 2048) return undefined;
   if (!value.startsWith("/")) return undefined;
   if (value.startsWith("//")) return undefined;
+  if (value.includes("\\") || /%5c/i.test(value)) return undefined;
   if (/[\x00-\x1f]/.test(value) || /%[01][0-9a-f]/i.test(value)) return undefined;
   return value;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/replit_integrations/auth/replitAuth.ts` around lines 16 - 23,
sanitizeReturnTo currently permits backslash variants (e.g., "/\evil.com" or
"%5C") that can be normalized into protocol-relative redirects; update
sanitizeReturnTo to reject any value containing backslash characters or their
percent-encoded forms by adding checks for "\\" and for percent-encoded
backslash sequences (case-insensitive "%5C"), and return undefined if either is
present so that sanitizeReturnTo reliably blocks these open-redirect vectors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/replit_integrations/auth/replitAuth.test.ts`:
- Around line 85-87: The current test for sanitizeReturnTo only checks a leading
backslash variant; add explicit cases for the exploitable normalized shapes by
asserting sanitizeReturnTo("/\\evil.com") and sanitizeReturnTo("/%5Cevil.com")
also return undefined so both the raw backslash and its percent-encoded form are
rejected; update the test block in replitAuth.test.ts (the "rejects
backslash-prefixed paths" it) to include these two expect(...) assertions
referencing sanitizeReturnTo.

In `@server/replit_integrations/auth/replitAuth.ts`:
- Around line 130-133: The code sets (req.session as any).returnTo only when
sanitizeReturnTo(req.query.returnTo) returns a value but never clears a
previously stored value; update the logic in replitAuth.ts so that after calling
sanitizeReturnTo(req.query.returnTo) you explicitly remove or clear (req.session
as any).returnTo (e.g., delete or set undefined) when sanitizeReturnTo returns
falsy, ensuring stale returnTo values are not reused across logins while
continuing to set it when valid; reference the sanitizeReturnTo call and
(req.session as any).returnTo in your change.

---

Duplicate comments:
In `@server/replit_integrations/auth/replitAuth.ts`:
- Around line 16-23: sanitizeReturnTo currently permits backslash variants
(e.g., "/\evil.com" or "%5C") that can be normalized into protocol-relative
redirects; update sanitizeReturnTo to reject any value containing backslash
characters or their percent-encoded forms by adding checks for "\\" and for
percent-encoded backslash sequences (case-insensitive "%5C"), and return
undefined if either is present so that sanitizeReturnTo reliably blocks these
open-redirect vectors.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3daa5c72-d0ab-4473-89e8-78810372a522

📥 Commits

Reviewing files that changed from the base of the PR and between f039544 and f1bb113.

📒 Files selected for processing (2)
  • server/replit_integrations/auth/replitAuth.test.ts
  • server/replit_integrations/auth/replitAuth.ts

Address CodeRabbit review comments:
- Reject backslash characters and %5C in sanitizeReturnTo to prevent
  browser normalization of /\evil.com into //evil.com
- Clear stale returnTo from session when login has no/invalid value
- Add test cases for /\evil.com, /%5Cevil.com, /%5cevil.com

https://claude.ai/code/session_011668oYNRLot8LEzJF1wqjA
@bd73-com bd73-com merged commit 5c1b85d into main Mar 25, 2026
1 check passed
@bd73-com bd73-com deleted the claude/fix-extension-login-issue-wcRSZ branch March 25, 2026 13:52
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