fix(auth): fix password reset broken by PKCE code verifier mismatch#40
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note
|
| 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
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 | 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.
Comment @coderabbitai help to get the list of available commands and usage tips.
Problem
Password reset was broken. Clicking the reset link always showed "This reset link is invalid or has expired."
Root cause:
@supabase/ssrstores the PKCEcode_verifierin a cookie, not localStorage. The reset email linked directly to/auth/reset-password, whereexchangeCodeForSession(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: changedredirectToto/auth/callback?next=/auth/reset-passwordso the server-side callback route handles the code exchange (it has access to the cookie-stored verifier)auth/reset-password/page.tsx: replacedexchangeCodeForSession(code)withgetUser()— by the time the user lands on this page, the session is already established by the callbackTest plan
/app/auth/reset-passwordwithout a valid session still shows the invalid state🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes