Skip to content

Instantly share code, notes, and snippets.

@juanqui
Created October 25, 2025 20:28
Show Gist options
  • Select an option

  • Save juanqui/70169e165b5f27e346a6e44062e1d3b8 to your computer and use it in GitHub Desktop.

Select an option

Save juanqui/70169e165b5f27e346a6e44062e1d3b8 to your computer and use it in GitHub Desktop.

Findings (root cause)

  • Supabase invite emails default to ConfirmationURL (…/auth/v1/verify?token=…&type=invite&redirect_to=…). After verification, Supabase redirects to your site with tokens in the URL hash (#access_token=…). That requires client-side handling; otherwise no session/cookie is set and users land unauthenticated on your homepage.
  • Your app has a server route at app/auth/confirm/route.ts that expects token_hash and type query params and calls supabase.auth.verifyOtp({ type, token_hash }) to set cookies server-side—but the current invite link doesn’t hit that route and uses token (not token_hash).
  • Net: there’s a mismatch between the link Supabase sends and how your app expects to finalize auth; you’re not processing the hash fragment, so the invite appears to “do nothing”.

What “should” happen

Proposal (recommended, minimal change)

  • Update the “Invitation” email template in Supabase (Auth → Email Templates) to link directly to your server confirm route using TokenHash:
  {{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=invite&next=/auth/update-password
  • Ensure https://resumeforge-alpha.vercel.app/auth/confirm and /auth/update-password are in your Redirect URLs allow-list.
  • Your existing app/auth/confirm/route.ts will run verifyOtp, set Supabase cookies server-side, and redirect to /auth/update-password where the user can set their password (components/auth/update-password-form.tsx uses supabase.auth.updateUser({ password })).
  • Also update the “Recovery” (and optionally “Signup”) templates similarly:
  {{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=recovery&next=/auth/update-password

Alternative (if you prefer to keep default ConfirmationURL)

  • Add a client page (e.g., /auth/callback) that runs on mount:
  // Client component
  const supabase = createClient();
  await supabase.auth.getSessionFromUrl({ storeSession: true });
  // then navigate to onboarding/update-password, and optionally hit an API route to sync cookies if needed
  • Change redirect_to in the invite to /auth/callback. This works, but you must ensure cookie sync for SSR (e.g., call a tiny route that reads Authorization bearer and sets Supabase cookies), whereas your current verifyOtp server route already handles cookies cleanly.

Checks

  • Auth settings: keep Email sign-ups disabled; verify Redirect URLs allow-list includes the routes above.
  • Test: send an invite → click email → ensure app/auth/confirm/route.ts is hit (no hash in URL), cookies set, and you land on /auth/update-password to complete onboarding.

Why this fixes it

  • You align the email link to your server-side confirm flow (token_hash + type), avoiding the hash fragment and ensuring the session is created and cookies are set in one step. This matches Supabase’s documented pattern for custom email links with verifyOtp.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment