Learn how to implement authentication in your Next.js application using Supabase Auth. This guide covers database setup, middleware configuration, and route protection.
The following authentication routes are pre-implemented and ready to use:
| Route | Description |
|---|---|
| /sign-in | Sign in page with email/password and social providers |
| /sign-up | Sign up page for new users |
| /forgot-password | Password reset request page |
| /private/reset-password | Protected route for password reset |
Create the necessary tables in your Supabase database for user management. This includes the profiles table and admin flags.
1-- Create a table for user profiles2create table public.profiles (3 id uuid references auth.users on delete cascade primary key,4 username text unique,5 full_name text,6 avatar_url text,7 is_admin boolean default false,8 created_at timestamp with time zone default timezone('utc'::text, now()) not null,9 updated_at timestamp with time zone default timezone('utc'::text, now()) not null10);1112-- Enable Row Level Security13alter table public.profiles enable row level security;1415-- Create a trigger to automatically create a profile for new users16create or replace function public.handle_new_user()17returns trigger as $$18begin19 insert into public.profiles (id, username, full_name, avatar_url)20 values (21 new.id,22 new.raw_user_meta_data->>'username',23 new.raw_user_meta_data->>'full_name',24 new.raw_user_meta_data->>'avatar_url'25 );26 return new;27end;28$$ language plpgsql security definer;2930create trigger on_auth_user_created31 after insert on auth.users32 for each row execute procedure public.handle_new_user();3334-- Update updated_at when profile is modified35create trigger handle_updated_at before update on public.profiles36 for each row execute procedure moddatetime (updated_at);
Use Next.js middleware to protect routes and handle authentication state. The middleware runs before requests are completed, making it perfect for auth checks and redirects.
1import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'2import { NextResponse } from 'next/server'3import type { NextRequest } from 'next/server'4import type { Database } from '@/lib/database.types'56export async function middleware(req: NextRequest) {7 const res = NextResponse.next()8 const supabase = createMiddlewareClient<Database>({ req, res })9 const {10 data: { session },11 } = await supabase.auth.getSession()1213 // Authentication check14 if (!session && req.nextUrl.pathname.startsWith('/dashboard')) {15 return NextResponse.redirect(new URL('/sign-in', req.url))16 }1718 // Admin check19 if (20 req.nextUrl.pathname.startsWith('/admin') &&21 session?.user.user_metadata.is_admin !== true22 ) {23 return NextResponse.redirect(new URL('/', req.url))24 }2526 // Public routes when authenticated27 if (28 session &&29 (req.nextUrl.pathname === '/sign-in' ||30 req.nextUrl.pathname === '/sign-up')31 ) {32 return NextResponse.redirect(new URL('/dashboard', req.url))33 }3435 return res36}3738export const config = {39 matcher: [40 '/dashboard/:path*',41 '/admin/:path*',42 '/sign-in',43 '/sign-up',44 '/private/:path*',45 ],46}
Implement Row Level Security (RLS) policies to control access to your data at the database level.
1-- Profiles policies2create policy "Public profiles are viewable by everyone"3 on public.profiles for select4 using ( true );56create policy "Users can update their own profile"7 on public.profiles for update8 using ( auth.uid() = id );910-- Admin policies11create policy "Only admins can delete profiles"12 on public.profiles for delete13 using (14 auth.uid() in (15 select id from public.profiles16 where is_admin = true17 )18 );1920-- Function to check if user is admin21create or replace function auth.is_admin()22returns boolean as $$23 select exists(24 select 1 from public.profiles25 where id = auth.uid()26 and is_admin = true27 );28$$ language sql security definer;
Use these hooks to manage authentication state and protect routes in your application.
1import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'2import { useRouter } from 'next/navigation'3import { useEffect, useState } from 'react'4import type { User } from '@supabase/auth-helpers-nextjs'5import type { Database } from '@/lib/database.types'67export function useAuth() {8 const [user, setUser] = useState<User | null>(null)9 const [loading, setLoading] = useState(true)10 const router = useRouter()11 const supabase = createClientComponentClient<Database>()1213 useEffect(() => {14 const {15 data: { subscription },16 } = supabase.auth.onAuthStateChange((event, session) => {17 setUser(session?.user ?? null)18 setLoading(false)1920 if (event === 'SIGNED_OUT') {21 router.push('/sign-in')22 }23 })2425 return () => {26 subscription.unsubscribe()27 }28 }, [router, supabase])2930 return { user, loading }31}3233// Usage in a protected component34export function ProtectedComponent() {35 const { user, loading } = useAuth()3637 if (loading) {38 return <div>Loading...</div>39 }4041 if (!user) {42 return null43 }4445 return <div>Protected content for {user.email}</div>46}