Skip to content

fix(auth): fix password reset broken by PKCE code verifier mismatch#40

Merged
MinitJain merged 1 commit into
mainfrom
fix/reset-password-pkce
Apr 3, 2026
Merged

fix(auth): fix password reset broken by PKCE code verifier mismatch#40
MinitJain merged 1 commit into
mainfrom
fix/reset-password-pkce

Conversation

@MinitJain
Copy link
Copy Markdown
Owner

@MinitJain MinitJain commented Apr 3, 2026

Problem

Password reset was broken. Clicking the reset link always showed "This reset link is invalid or has expired."

Root cause: @supabase/ssr stores the PKCE code_verifier in a cookie, not localStorage. The reset email linked directly to /auth/reset-password, where exchangeCodeForSession(code) was called client-side. The client-side Supabase JS looks for the verifier in localStorage — it's not there — so the exchange always failed.

Fix

  • auth/page.tsx: changed redirectTo to /auth/callback?next=/auth/reset-password so the server-side callback route handles the code exchange (it has access to the cookie-stored verifier)
  • auth/reset-password/page.tsx: replaced exchangeCodeForSession(code) with getUser() — by the time the user lands on this page, the session is already established by the callback

Test plan

  • Request a password reset
  • Click the link in the email
  • Confirm the "Set new password" form appears (not the invalid link error)
  • Set a new password and confirm redirect to /app
  • Navigating directly to /auth/reset-password without a valid session still shows the invalid state

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Enhanced password reset authentication flow with improved callback routing and parameter handling.
    • Strengthened session validation during password reset by switching to server-side verification for increased security.

The reset email linked directly to /auth/reset-password, where
exchangeCodeForSession was called client-side. The PKCE code_verifier
is stored in a cookie (by @supabase/ssr), not localStorage, so the
client-side exchange always failed with "invalid or expired".

Fix: redirect to /auth/callback?next=/auth/reset-password so the
server-side callback route exchanges the code using the cookie-stored
verifier, then on the reset page check getUser() for an active session
instead of exchanging the code again.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 3, 2026

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

Project Deployment Actions Updated (UTC)
recall Ready Ready Preview, Comment Apr 3, 2026 7:42am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 3, 2026

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'instructions'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6298aa42-94b4-49b3-8a71-ad096ef11bcd

📥 Commits

Reviewing files that changed from the base of the PR and between c745501 and 0f9fdab.

📒 Files selected for processing (2)
  • client/src/app/auth/page.tsx
  • client/src/app/auth/reset-password/page.tsx

📝 Walkthrough

Walkthrough

Password reset flow updated across two endpoints: the initial password reset request now redirects through an auth callback handler with a next parameter, and the reset password page switched from client-side code exchange to server-side session validation via getUser().

Changes

Cohort / File(s) Summary
Password Reset Flow
client/src/app/auth/page.tsx, client/src/app/auth/reset-password/page.tsx
Modified redirect URL in handleForgotPassword to route through /auth/callback?next=/auth/reset-password. Replaced client-side recovery code extraction and exchangeCodeForSession() with server-side user session validation using supabase.auth.getUser(), now setting status based on user existence rather than code exchange success.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant AuthPage as /auth Page
    participant SupabaseAuth as Supabase Auth
    participant AuthCallback as /auth/callback
    participant ResetPage as /auth/reset-password Page
    
    User->>AuthPage: Initiates password reset
    AuthPage->>SupabaseAuth: resetPasswordForEmail(email, redirectTo: /auth/callback?next=/auth/reset-password)
    SupabaseAuth->>User: Sends reset email with code
    User->>AuthCallback: Clicks reset link with code
    AuthCallback->>SupabaseAuth: Processes code
    AuthCallback->>ResetPage: Redirects to /auth/reset-password
    ResetPage->>SupabaseAuth: getUser()
    alt User session valid
        SupabaseAuth-->>ResetPage: Returns user
        ResetPage->>ResetPage: Sets status to "ready"
    else No valid session
        SupabaseAuth-->>ResetPage: No user or error
        ResetPage->>ResetPage: Sets status to "invalid"
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • feat(auth): forgot password + reset password page #37: Implemented the original reset-password flow with client-side code exchange and direct redirect to /auth/reset-password, which these changes now refactor to route through a callback endpoint and use server-side validation.
  • M7: Supabase Auth #11: Added foundational Supabase auth integration in auth pages and flows that these changes directly modify with alternative session validation approach.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Title check ✅ Passed The title clearly and specifically describes the main change: fixing a password reset issue caused by PKCE code verifier mismatch, which is the core focus of the changeset.
Description check ✅ Passed The description is comprehensive and well-structured, covering the problem, root cause, implemented fixes, and test plan. However, the checklist items (Code builds, TypeScript errors, Lint passes, Self-reviewed, Tested manually) are all unchecked, indicating incomplete verification.

✏️ 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 fix/reset-password-pkce

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.

@MinitJain MinitJain merged commit 580abde into main Apr 3, 2026
4 checks passed
@MinitJain MinitJain deleted the fix/reset-password-pkce branch April 3, 2026 07:44
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