Added Claude Skills
This commit is contained in:
@@ -0,0 +1,384 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user