tasq/.claude/skills/moai-platform-supabase/modules/auth-integration.md

385 lines
8.3 KiB
Markdown

---
name: auth-integration
description: Authentication patterns and JWT handling for Supabase applications
parent-skill: moai-platform-supabase
version: 1.0.0
updated: 2026-01-06
---
# Auth Integration Module
## Overview
Supabase Auth provides authentication with multiple providers, JWT-based sessions, and seamless integration with Row-Level Security policies.
## Client Setup
### Browser Client
```typescript
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
```
### Server-Side Client (Next.js App Router)
```typescript
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
import { Database } from './database.types'
export function createServerSupabase() {
const cookieStore = cookies()
return createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value
},
set(name, value, options) {
cookieStore.set({ name, value, ...options })
},
remove(name, options) {
cookieStore.set({ name, value: '', ...options })
}
}
}
)
}
```
### Middleware Client (Next.js)
```typescript
// middleware.ts
import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function middleware(request: NextRequest) {
let response = NextResponse.next({
request: { headers: request.headers }
})
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return request.cookies.get(name)?.value
},
set(name: string, value: string, options: CookieOptions) {
request.cookies.set({ name, value, ...options })
response.cookies.set({ name, value, ...options })
},
remove(name: string, options: CookieOptions) {
request.cookies.set({ name, value: '', ...options })
response.cookies.set({ name, value: '', ...options })
}
}
}
)
await supabase.auth.getUser()
return response
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)']
}
```
## Authentication Methods
### Email/Password Sign Up
```typescript
async function signUp(email: string, password: string) {
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`
}
})
if (error) throw error
return data
}
```
### Email/Password Sign In
```typescript
async function signIn(email: string, password: string) {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password
})
if (error) throw error
return data
}
```
### OAuth Provider
```typescript
async function signInWithGoogle() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
queryParams: {
access_type: 'offline',
prompt: 'consent'
}
}
})
if (error) throw error
return data
}
```
### Magic Link
```typescript
async function signInWithMagicLink(email: string) {
const { data, error } = await supabase.auth.signInWithOtp({
email,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`
}
})
if (error) throw error
return data
}
```
## Session Management
### Get Current User
```typescript
async function getCurrentUser() {
const { data: { user }, error } = await supabase.auth.getUser()
if (error) throw error
return user
}
```
### Get Session
```typescript
async function getSession() {
const { data: { session }, error } = await supabase.auth.getSession()
if (error) throw error
return session
}
```
### Sign Out
```typescript
async function signOut() {
const { error } = await supabase.auth.signOut()
if (error) throw error
}
```
### Listen to Auth Changes
```typescript
useEffect(() => {
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
if (event === 'SIGNED_IN') {
console.log('User signed in:', session?.user)
}
if (event === 'SIGNED_OUT') {
console.log('User signed out')
}
if (event === 'TOKEN_REFRESHED') {
console.log('Token refreshed')
}
}
)
return () => subscription.unsubscribe()
}, [])
```
## Auth Callback Handler
### Next.js App Router
```typescript
// app/auth/callback/route.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
import { NextResponse, type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const { searchParams, origin } = new URL(request.url)
const code = searchParams.get('code')
const next = searchParams.get('next') ?? '/'
if (code) {
const cookieStore = cookies()
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value
},
set(name, value, options) {
cookieStore.set({ name, value, ...options })
},
remove(name, options) {
cookieStore.set({ name, value: '', ...options })
}
}
}
)
const { error } = await supabase.auth.exchangeCodeForSession(code)
if (!error) {
return NextResponse.redirect(`${origin}${next}`)
}
}
return NextResponse.redirect(`${origin}/auth/error`)
}
```
## Protected Routes
### Server Component Protection
```typescript
// app/dashboard/page.tsx
import { redirect } from 'next/navigation'
import { createServerSupabase } from '@/lib/supabase/server'
export default async function DashboardPage() {
const supabase = createServerSupabase()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
redirect('/login')
}
return <Dashboard user={user} />
}
```
### Client Component Protection
```typescript
'use client'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import { supabase } from '@/lib/supabase/client'
export function useRequireAuth() {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
const router = useRouter()
useEffect(() => {
supabase.auth.getUser().then(({ data: { user } }) => {
if (!user) {
router.push('/login')
} else {
setUser(user)
}
setLoading(false)
})
}, [router])
return { user, loading }
}
```
## Custom Claims
### Setting Custom Claims (Edge Function)
```typescript
// supabase/functions/set-claims/index.ts
serve(async (req) => {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
const { userId, claims } = await req.json()
// Update user metadata (available in JWT)
const { error } = await supabase.auth.admin.updateUserById(userId, {
app_metadata: claims
})
if (error) throw error
return new Response(JSON.stringify({ success: true }))
})
```
### Reading Claims in RLS
```sql
-- Access claims in RLS policies
CREATE POLICY "admin_only" ON admin_data FOR ALL
USING ((auth.jwt() ->> 'role')::text = 'admin');
```
## Password Reset
```typescript
async function resetPassword(email: string) {
const { data, error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/auth/reset-password`
})
if (error) throw error
return data
}
async function updatePassword(newPassword: string) {
const { data, error } = await supabase.auth.updateUser({
password: newPassword
})
if (error) throw error
return data
}
```
## Context7 Query Examples
For latest Auth documentation:
Topic: "supabase auth signIn signUp"
Topic: "supabase ssr server client"
Topic: "auth jwt claims custom"
---
Related Modules:
- row-level-security.md - Auth integration with RLS
- typescript-patterns.md - Type-safe auth patterns
- edge-functions.md - Server-side auth verification