DEV Community
Grade 8
2h ago
Supabase Authentication & Authorization Patterns
Supabase Authentication & Authorization Patterns Authentication and authorization are the foundation of secure applications. Supabase provides a complete auth system with email/password, social logins, magic links, and powerful row-level security. This guide covers everything you need to build secure, production-ready authentication. Why Supabase Auth? Built-in Features: Email/password authentication Social OAuth providers (Google, GitHub, etc.) Magic link (passwordless) authentication Multi-factor authentication (2FA) Session management JWT tokens Security: Row-level security (RLS) policies Automatic password hashing Secure session storage PKCE flow for OAuth Developer Experience: Simple API Auto-generated types Real-time auth state changes Works seamlessly with Next.js 1. Supabase Auth Fundamentals Client Setup // lib/supabase/client.ts import { createBrowserClient } from ' @supabase/ssr ' export function createClient () { return createBrowserClient ( process . env . NEXT_PUBLIC_SUPABASE_URL ! , process . env . NEXT_PUBLIC_SUPABASE_ANON_KEY ! ) } Server Setup // lib/supabase/server.ts import { createServerClient } from ' @supabase/ssr ' import { cookies } from ' next/headers ' export function createClient () { 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 }, }, } ) } Auth State Management ' use client ' import { createClient } from ' @/lib/supabase/client ' import { useEffect , useState } from ' react ' import type { User } from ' @supabase/supabase-js ' export function useUser () { const [ user , setUser ] = useState < User | null > ( null ) const supabase = createClient () useEffect (() => { // Get initial session supabase . auth . getSession (). then (({ data : { session } }) => { setUser ( session ?. user ?? null ) }) // Listen for auth changes const { data : { subscription }, } = supabase . auth . onAuthStateChange (( _event , session ) => { setUser ( session ?. user ?? null ) }) return () => subscription . unsubscribe () }, []) return user } 2. Email/Password Authentication Sign Up ' use client ' import { createClient } from ' @/lib/supabase/client ' import { useState } from ' react ' export function SignUpForm () { const [ email , setEmail ] = useState ( '' ) const [ password , setPassword ] = useState ( '' ) const [ loading , setLoading ] = useState ( false ) const supabase = createClient () async function handleSignUp ( e : React . FormEvent ) { e . preventDefault () setLoading ( true ) const { data , error } = await supabase . auth . signUp ({ email , password , options : { emailRedirectTo : ` ${ window . location . origin } /auth/callback` , }, }) if ( error ) { alert ( error . message ) } else { alert ( ' Check your email for the confirmation link! ' ) } setLoading ( false ) } return ( < form onSubmit = { handleSignUp } > < input type = " email " placeholder = " Email " value = { email } onChange = {( e ) => setEmail ( e . target . value )} required /> < input type = " password " placeholder = " Password " value = { password } onChange = {( e ) => setPassword ( e . target . value )} required minLength = { 6 } / > < button type = " submit " disabled = { loading } > { loading ? ' Signing up... ' : ' Sign Up ' } < /button > < /form > ) } Sign In ' use client ' import { createClient } from ' @/lib/supabase/client ' import { useState } from ' react ' import { useRouter } from ' next/navigation ' export function SignInForm () { const [ email , setEmail ] = useState ( '' ) const [ password , setPassword ] = useState ( '' ) const [ loading , setLoading ] = useState ( false ) const router = useRouter () const supabase = createClient () async function handleSignIn ( e : React . FormEvent ) { e . preventDefault () setLoading ( true ) const { data , error } = await supabase . auth . signInWithPassword ({ email , password , }) if ( error ) { alert ( error . message ) setLoading ( false ) } else { router . push ( ' /dashboard ' ) router . refresh () } } return ( < form onSubmit = { handleSignIn } > < input type = " email " placeholder = " Email " value = { email } onChange = {( e ) => setEmail ( e . target . value )} required /> < input type = " password " placeholder = " Password " value = { password } onChange = {( e ) => setPassword ( e . target . value )} required /> < button type = " submit " disabled = { loading } > { loading ? ' Signing in... ' : ' Sign In ' } < /button > < /form > ) } Password Reset // Request password reset const { error } = await supabase . auth . resetPasswordForEmail ( email , { redirectTo : ` ${ window . location . origin } /auth/reset-password` , }) // Update password (on reset page) const { error } = await supabase . auth . updateUser ({ password : newPassword , }) Related: Fix Supabase Auth Session Not Persisting After Refresh Next.js 14, Supabase Auth Redirect Not Working Next.js App Route
Supabase Authentication & Authorization Patterns Authentication and authorization are the foundation of secure applications. Supabase provides a complete auth system with email/password, social logins, magic links, and powerful row-level security. This guide covers everything you need to build secure, production-ready authentication. Why Supabase Auth? Built-in Features: - Email/password authentication - Social OAuth providers (Google, GitHub, etc.) - Magic link (passwordless) authentication - Multi-factor authentication (2FA) - Session management - JWT tokens Security: - Row-level security (RLS) policies - Automatic password hashing - Secure session storage - PKCE flow for OAuth Developer Experience: - Simple API - Auto-generated types - Real-time auth state changes - Works seamlessly with Next.js 1. Supabase Auth Fundamentals Client Setup // lib/supabase/client.ts import { createBrowserClient } from '@supabase/ssr' export function createClient() { return createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! ) } Server Setup // lib/supabase/server.ts import { createServerClient } from '@supabase/ssr' import { cookies } from 'next/headers' export function createClient() { 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 }, }, } ) } Auth State Management 'use client' import { createClient } from '@/lib/supabase/client' import { useEffect, useState } from 'react' import type { User } from '@supabase/supabase-js' export function useUser() { const [user, setUser] = useState (null) const supabase = createClient() useEffect(() => { // Get initial session supabase.auth.getSession().then(({ data: { session } }) => { setUser(session?.user ?? null) }) // Listen for auth changes const { data: { subscription }, } = supabase.auth.onAuthStateChange((_event, session) => { setUser(session?.user ?? null) }) return () => subscription.unsubscribe() }, []) return user } 2. Email/Password Authentication Sign Up 'use client' import { createClient } from '@/lib/supabase/client' import { useState } from 'react' export function SignUpForm() { const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [loading, setLoading] = useState(false) const supabase = createClient() async function handleSignUp(e: React.FormEvent) { e.preventDefault() setLoading(true) const { data, error } = await supabase.auth.signUp({ email, password, options: { emailRedirectTo: `${window.location.origin}/auth/callback`, }, }) if (error) { alert(error.message) } else { alert('Check your email for the confirmation link!') } setLoading(false) } return ( setEmail(e.target.value)} required /> setPassword(e.target.value)} required minLength={6} /> {loading ? 'Signing up...' : 'Sign Up'} ) } Sign In 'use client' import { createClient } from '@/lib/supabase/client' import { useState } from 'react' import { useRouter } from 'next/navigation' export function SignInForm() { const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [loading, setLoading] = useState(false) const router = useRouter() const supabase = createClient() async function handleSignIn(e: React.FormEvent) { e.preventDefault() setLoading(true) const { data, error } = await supabase.auth.signInWithPassword({ email, password, }) if (error) { alert(error.message) setLoading(false) } else { router.push('/dashboard') router.refresh() } } return ( setEmail(e.target.value)} required /> setPassword(e.target.value)} required /> {loading ? 'Signing in...' : 'Sign In'} ) } Password Reset // Request password reset const { error } = await supabase.auth.resetPasswordForEmail(email, { redirectTo: `${window.location.origin}/auth/reset-password`, }) // Update password (on reset page) const { error } = await supabase.auth.updateUser({ password: newPassword, }) Related: Fix Supabase Auth Session Not Persisting After Refresh Next.js 14, Supabase Auth Redirect Not Working Next.js App Router Solution 3. Social Authentication Configure OAuth Providers In Supabase Dashboard: - Go to Authentication → Providers - Enable desired providers (Google, GitHub, etc.) - Add OAuth credentials from provider Google OAuth 'use client' import { createClient } from '@/lib/supabase/client' export function GoogleSignIn() { const supabase = createClient() async function handleGoogleSignIn() { const { data, error } = await supabase.auth.signInWithOAuth({ provider: 'google', options: { redirectTo: `${window.location.origin}/auth/callback`, queryParams: { access_type: 'offline', prompt: 'consent', }, }, }) if (error) { alert(error.message) } } return ( Sign in with Google ) } GitHub OAuth const { data, error } = await supabase.auth.signInWithOAuth({ provider: 'github', options: { redirectTo: `${window.location.origin}/auth/callback`, }, }) OAuth Callback Handler // app/auth/callback/route.ts import { createClient } from '@/lib/supabase/server' import { NextResponse } from 'next/server' export async function GET(request: Request) { const requestUrl = new URL(request.url) const code = requestUrl.searchParams.get('code') if (code) { const supabase = createClient() await supabase.auth.exchangeCodeForSession(code) } return NextResponse.redirect(new URL('/dashboard', request.url)) } Related: Implement Social Auth with Supabase Next.js Complete Guide, Supabase OAuth Providers Configuration 4. Magic Link Authentication Send Magic Link 'use client' import { createClient } from '@/lib/supabase/client' import { useState } from 'react' export function MagicLinkForm() { const [email, setEmail] = useState('') const [loading, setLoading] = useState(false) const [sent, setSent] = useState(false) const supabase = createClient() async function handleMagicLink(e: React.FormEvent) { e.preventDefault() setLoading(true) const { error } = await supabase.auth.signInWithOtp({ email, options: { emailRedirectTo: `${window.location.origin}/auth/callback`, }, }) if (error) { alert(error.message) } else { setSent(true) } setLoading(false) } if (sent) { return Check your email for the magic link! } return ( setEmail(e.target.value)} required /> {loading ? 'Sending...' : 'Send Magic Link'} ) } Related: Implement Supabase Magic Link Auth in Next.js 15, Supabase Email Confirmation Not Sending Troubleshooting Guide 5. Multi-Factor Authentication (2FA) Enable 2FA 'use client' import { createClient } from '@/lib/supabase/client' import { useState } from 'react' export function Enable2FA() { const [qrCode, setQrCode] = useState('') const [secret, setSecret] = useState('') const supabase = createClient() async function enrollMFA() { const { data, error } = await supabase.auth.mfa.enroll({ factorType: 'totp', }) if (error) { alert(error.message) return } setQrCode(data.totp.qr_code) setSecret(data.totp.secret) } async function verifyMFA(code: string) { const { data, error } = await supabase.auth.mfa.challengeAndVerify({ factorId: data.id, code, }) if (error) { alert(error.message) } else { alert('2FA enabled successfully!') } } return ( Enable 2FA {qrCode && ( Secret: {secret} { if (e.target.value.length === 6) { verifyMFA(e.target.value) } }} /> )} ) } Related: Add Two-Factor Authentication 2FA with Supabase, Build Custom Auth Flow with Supabase and Next.js 6. Row-Level Security (RLS) Policies Enable RLS -- Enable RLS on table ALTER TABLE posts ENABLE ROW LEVEL SECURITY; Basic RLS Policies -- Users can only read their own posts CREATE POLICY "Users can view own posts" ON posts FOR SELECT USING (auth.uid() = user_id); -- Users can insert their own posts CREATE POLICY "Users can create posts" ON posts FOR INSERT WITH CHECK (auth.uid() = user_id); -- Users can update their own posts CREATE POLICY "Users can update own posts" ON posts FOR UPDATE USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id); -- Users can delete their own posts CREATE POLICY "Users can delete own posts" ON posts FOR DELETE USING (auth.uid() = user_id); Public Read, Authenticated Write -- Anyone can read posts CREATE POLICY "Public posts are viewable by everyone" ON posts FOR SELECT USING (published = true); -- Only authenticated users can create posts CREATE POLICY "Authenticated users can create posts" ON posts FOR INSERT WITH CHECK (auth.role() = 'authenticated'); Organization-Based Access -- Users can only see posts from their organization CREATE POLICY "Users can view organization posts" ON posts FOR SELECT USING ( organization_id IN ( SELECT organization_id FROM organization_members WHERE user_id = auth.uid() ) ); Related: Implement Supabase Row Level Security RLS Policies, Supabase RLS Policies Common Mistakes and Solutions 7. Custom Claims and Permissions Add Custom Claims -- Create function to get user role CREATE OR REPLACE FUNCTION auth.user_role() RETURNS TEXT AS $$ SELECT role FROM public.users WHERE id = auth.uid() $$ LANGUAGE SQL STABLE; -- Use in RLS policy CREATE POLICY "Admins can view all posts" ON posts FOR SELECT USING (auth.user_role() = 'admin'); Role-Based Access Control (RBAC) -- Create roles table CREATE TABLE roles ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT UNIQUE NOT NULL, permissions JSONB NOT NULL DEFAULT '{}'::jsonb ); -- Create user_roles table CREATE TABLE user_roles ( user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, role_id UUID REFERENCES roles(id) ON DELETE CASCADE, PRIMARY KEY (user_id, role_id) ); -- Function to check permission CREATE OR REPLACE FUNCTION has_permission(permission TEXT) RETURNS BOOLEAN AS $$ SELECT EXISTS ( SELECT 1 FROM user_roles ur JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = auth.uid() AND r.permissions ? permission ) $$ LANGUAGE SQL STABLE; -- Use in RLS policy CREATE POLICY "Users with edit permission can update posts" ON posts FOR UPDATE USING (has_permission('posts.edit')); Related: Implement Role-Based Access Control RBAC Supabase, Handle Supaba
Comments
No comments yet. Start the discussion.