This guide covers how to set up and use Supabase Authentication in this project.
This project uses Supabase Auth for user authentication, which provides:
auth.users (Managed by Supabase)public.profiles (Custom)auth.users with additional user dataid (UUID) - References auth.users(id)email (TEXT) - User emaildisplay_name (TEXT) - User display namephoto_url (TEXT) - Profile photo URLcreated_at (TIMESTAMPTZ) - Creation timestampupdated_at (TIMESTAMPTZ) - Last update timestamppublic.admin_users (Custom)id (UUID) - Primary keyemail (TEXT) - Admin emailcreated_at (TIMESTAMPTZ) - Creation timestampNo additional configuration needed. Users can sign up with email/password out of the box.
To enable Google, GitHub, or other OAuth providers:
Use these steps to create the OAuth client inside Google:
http://localhost:3000https://your-production-domain.comhttp://localhost:3000/auth/callbackhttps://your-production-domain.com/auth/callbackhttps://<your-project-ref>.supabase.co/auth/v1/callback (Supabase uses this internally, so include it as well.)Supabase will handle the final redirect using whatever
redirectToyou pass insignInWithOAuth. In our code we always usewindow.location.origin, so once Google lists the origins/URIs above the callback will return to the current environment instead of any hard-coded URL.
Ensure these are set in .env.local:
# Required
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
# For admin operations (server-side only)
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
# Admin configuration (for session encryption)
ADMIN_AUTH_SECRET=your-secret-key-hereInsert admin emails into the admin_users table:
INSERT INTO public.admin_users (email)
VALUES ('your-admin@example.com')
ON CONFLICT (email) DO NOTHING;import { createClient } from '@/app/lib/supabase/client'
const supabase = createClient()
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'securepassword',
options: {
data: {
display_name: 'John Doe',
},
},
})const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'securepassword',
})const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
})const { error } = await supabase.auth.signOut()// Client Components
const { data: { user } } = await supabase.auth.getUser()
// Server Components/Actions
import { createClient } from '@/app/lib/supabase/server'
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()'use client'
import { useEffect, useState } from 'react'
import { createClient } from '@/app/lib/supabase/client'
import type { User } from '@supabase/supabase-js'
export function MyComponent() {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
const supabase = createClient()
useEffect(() => {
// Get initial user
supabase.auth.getUser().then(({ data }) => {
setUser(data.user)
setLoading(false)
})
// Listen to auth changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setUser(session?.user ?? null)
}
)
return () => subscription.unsubscribe()
}, [supabase])
if (loading) return <div>Loading...</div>
if (!user) return <div>Not authenticated</div>
return <div>Welcome, {user.email}!</div>
}'use server'
import { createClient } from '@/app/lib/supabase/server'
export async function myServerAction() {
const supabase = await createClient()
const { data: { user }, error } = await supabase.auth.getUser()
if (error || !user) {
return { error: 'Unauthorized' }
}
// User is authenticated, proceed with action
return { success: true }
}'use server'
import { createClient } from '@/app/lib/supabase/server'
export async function checkIsAdmin() {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user?.email) return false
const { data } = await supabase
.from('admin_users')
.select('email')
.eq('email', user.email)
.single()
return !!data
}Never disable Row Level Security on tables that contain user data:
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;Always verify authentication on the server, never trust client-side:
// BAD - Client can manipulate this
const user = getClientUser()
await updateProfile(user.id, data)
// GOOD - Server verifies auth
const { data: { user } } = await supabase.auth.getUser()
if (!user) throw new Error('Unauthorized')
await updateProfile(user.id, data)The SUPABASE_SERVICE_ROLE_KEY bypasses RLS. Only use it for:
.env.local to gitSUPABASE_SERVICE_ROLE_KEY secret (server-side only)