--- 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( 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 } ``` ### 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