# Supabase Auth

> Guidelines for implementing Supabase Auth SSR in Next.js. Trigger: When implementing Supabase authentication.

- Skill: `odjaramillo/supabase-auth-2` (Agent Skill)
- Install (CLI): `npx skillmds@latest add odjaramillo/supabase-auth-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/odjaramillo/supabase-auth-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- License: Apache-2.0
- Author: odjaramillo (https://skillmd.com/u/odjaramillo)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/odjaramillo/supabase-auth-2

---


## 🚨 CRITICAL: DEPRECATED PATTERNS 🚨

**NEVER generate these patterns - they BREAK the application:**

```typescript
// ❌ NEVER USE - BREAKS APPLICATION
{
  cookies: {
    get(name: string) {                 // ❌ BREAKS
      return cookieStore.get(name)
    },
    set(name: string, value: string) {  // ❌ BREAKS
      cookieStore.set(name, value)
    },
    remove(name: string) {              // ❌ BREAKS
      cookieStore.remove(name)
    }
  }
}

// ❌ NEVER import from auth-helpers-nextjs - DEPRECATED
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'  // ❌
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'  // ❌
```

---

## Critical Patterns

### ALWAYS Use This Pattern (REQUIRED)

```typescript
// ✅ CORRECT - Only use getAll and setAll
{
  cookies: {
    getAll() {
      return cookieStore.getAll()
    },
    setAll(cookiesToSet) {
      cookiesToSet.forEach(({ name, value, options }) =>
        cookieStore.set(name, value, options)
      )
    }
  }
}
```

### Browser Client (REQUIRED)

```typescript
import { createBrowserClient } from '@supabase/ssr'

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}
```

### Server Client (REQUIRED)

```typescript
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
  const cookieStore = await cookies()

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll()
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            )
          } catch {
            // Called from Server Component - ignored if middleware refreshes sessions
          }
        },
      },
    }
  )
}
```

### Middleware (REQUIRED)

```typescript
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
  let supabaseResponse = NextResponse.next({ request })

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return request.cookies.getAll()
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value))
          supabaseResponse = NextResponse.next({ request })
          cookiesToSet.forEach(({ name, value, options }) =>
            supabaseResponse.cookies.set(name, value, options)
          )
        },
      },
    }
  )

  // IMPORTANT: DO NOT REMOVE auth.getUser()
  const { data: { user } } = await supabase.auth.getUser()

  if (
    !user &&
    !request.nextUrl.pathname.startsWith('/login') &&
    !request.nextUrl.pathname.startsWith('/auth')
  ) {
    const url = request.nextUrl.clone()
    url.pathname = '/login'
    return NextResponse.redirect(url)
  }

  // MUST return supabaseResponse as-is to avoid session sync issues
  return supabaseResponse
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
}
```

---

## Decision Tree

```
Need browser client?       → Use createBrowserClient from @supabase/ssr
Need server client?        → Use createServerClient with getAll/setAll
Need route protection?     → Use middleware with auth.getUser()
Need OAuth?                → Use signInWithOAuth
Need magic link?           → Use signInWithOtp
```

---

## Code Examples

### Sign Up / Sign In

```typescript
// Sign up
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password',
});

// Sign in
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'secure-password',
});

// Sign out
await supabase.auth.signOut();
```

### Session Management

```typescript
// Get current session
const { data: { session } } = await supabase.auth.getSession();

// Listen to auth changes
supabase.auth.onAuthStateChange((event, session) => {
  if (event === 'SIGNED_IN') {
    // Handle sign in
  }
});
```

---

## AI Verification Checklist

Before generating code, VERIFY:
1. ✅ Using ONLY `getAll` and `setAll`?
2. ✅ Importing from `@supabase/ssr`?
3. ❌ Any `get`, `set`, or `remove`? → FIX
4. ❌ Importing from `auth-helpers-nextjs`? → FIX

**Consequences of incorrect implementation:**
- Breaks in production
- Fails to maintain session state
- Causes authentication loops
- Security vulnerabilities

