Skip to content

[Bug / Auth] Fix GitHub OAuth redirect loop — patch @supabase/ssr callback handling (Bug #86) #128

Description

@basanth-p

Parent Epic

Part of #124 — Landing Page Polish, CTA/Form/Event Tracked Flows & Backend Integration Gaps

Blocks: #127 (CTA wiring is meaningless if OAuth is broken), #119 (Auth journey QA)


🐛 Bug Reference

This is the implementation fix for the issue reported in Bug #86.


🎯 Objective

The GitHub OAuth callback at app/auth/callback/route.ts is producing a redirect loop or 500 error after the @supabase/ssr migration in PR #106. This is the single most critical blocking bug in the entire project — the primary landing page CTA (/auth/login) leads directly into this broken flow.


🔍 Root Cause Diagnosis

The @supabase/ssr package handles cookies differently from @supabase/auth-helpers-nextjs. Common failure modes after migration:

Symptom Likely Root Cause
500 on /auth/callback createServerClient not receiving correct cookies() adapter from next/headers
Redirect loop (/auth/login/auth/callback/auth/login) Session not persisted — set-cookie header not written because cookies().set() is being called outside a Server Action or Route Handler boundary
AuthSessionMissingError on dashboard after login getUser() called before the session cookie is set in the response
Callback succeeds but user not onboarded correctly is_onboarded check in middleware running before Supabase session is fully established

🛠️ Tasks

1. Audit app/auth/callback/route.ts

  • Verify createServerClient is using cookies() from next/headers with both get and set methods
  • Ensure await supabase.auth.exchangeCodeForSession(code) is called before any redirect
  • Ensure the NextResponse being returned carries the updated Set-Cookie header from the Supabase client
  • Correct pattern:
// app/auth/callback/route.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const code = searchParams.get('code')
  const cookieStore = cookies()

  const response = NextResponse.redirect(new URL('/dashboard', request.url))

  if (code) {
    const supabase = createServerClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
      {
        cookies: {
          get: (name) => cookieStore.get(name)?.value,
          set: (name, value, options) => response.cookies.set({ name, value, ...options }),
          remove: (name, options) => response.cookies.set({ name, value: '', ...options }),
        },
      }
    )
    await supabase.auth.exchangeCodeForSession(code)
  }

  return response
}

2. Audit middleware.ts

  • Verify createServerClient in middleware also uses the cookie adapter pattern (not the old createMiddlewareClient)
  • The middleware must call supabase.auth.getUser() (not getSession()) — getSession() does not validate the JWT server-side
  • is_onboarded redirect logic: verify it reads from the Supabase users table, not from session metadata that may not be set during the callback

3. Audit app/dashboard/layout.tsx

  • Confirm protected route check uses getUser() not getSession()
  • Confirm redirect on null user goes to /auth/login?redirect=/dashboard so the post-login redirect works

4. Local reproduction

  • Reproduce the loop/500 locally with a test GitHub account
  • Apply the fix
  • Walk through the full OAuth flow: Click "Connect" → GitHub OAuth → Callback → /onboard or /dashboard with no errors

✅ Acceptance Criteria

  • GitHub OAuth completes without a 500 or redirect loop
  • User session cookie is set correctly after callback
  • New user: redirected to /onboard after first login
  • Returning user (is_onboarded = true): redirected to /dashboard
  • Console: no AuthSessionMissingError or unhandled exceptions
  • Network: /auth/callback returns a 302, not a 500
  • Bug [BUG] GItHub OAuth Persistent Sessions for Developers (not working) #86 closed after this merges

Metadata

Metadata

Assignees

Labels

Type

Fields

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions