The Complete Guide to Supabase Authentication
Secure, Scalable, Simple
Supabase provides a complete authentication system out of the box. Learn how to implement email/password auth, magic links, OAuth providers, and Row Level Security in your Next.js application.
Why Supabase for Authentication?
Building authentication from scratch is complex and error-prone. Supabase handles the heavy lifting with a secure, scalable authentication system that includes:
- Email and password authentication
- Passwordless magic links
- OAuth providers (Google, GitHub, etc.)
- Row Level Security (RLS) integration
- Session management
- Password reset and email verification
Setting Up Supabase
1. Create a Supabase Project
Head to supabase.com and create a new project. You'll need your project URL and anon key.
2. Install Dependencies
npm install @supabase/supabase-js @supabase/ssr3. Configure Environment Variables
# .env.local
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-keyCreating Supabase Clients
Browser Client
// 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 Client
// 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
},
},
}
)
}Implementing Authentication
Email & Password Sign Up
'use client'
import { createClient } from '@/lib/supabase/client'
export default function SignUpForm() {
async function handleSignUp(formData: FormData) {
const supabase = createClient()
const { data, error } = await supabase.auth.signUp({
email: formData.get('email') as string,
password: formData.get('password') as string,
})
if (error) {
console.error('Error:', error.message)
return
}
// Redirect or show success message
}
return (
<form action={handleSignUp}>
<input name="email" type="email" required />
<input name="password" type="password" required />
<button type="submit">Sign Up</button>
</form>
)
}Email & Password Sign In
async function handleSignIn(formData: FormData) {
const supabase = createClient()
const { data, error } = await supabase.auth.signInWithPassword({
email: formData.get('email') as string,
password: formData.get('password') as string,
})
if (error) {
console.error('Error:', error.message)
return
}
// Redirect to dashboard
window.location.href = '/dashboard'
}Magic Link Authentication
async function handleMagicLink(email: string) {
const supabase = createClient()
const { error } = await supabase.auth.signInWithOtp({
email,
options: {
emailRedirectTo: 'http://localhost:3000/auth/callback'
}
})
if (error) {
console.error('Error:', error.message)
return
}
// Show success message
alert('Check your email for the magic link!')
}OAuth Providers (Google, GitHub, etc.)
async function handleOAuthSignIn(provider: 'google' | 'github') {
const supabase = createClient()
const { error } = await supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo: 'http://localhost:3000/auth/callback'
}
})
if (error) {
console.error('Error:', error.message)
}
}Auth Callback Handler
Create a route to handle OAuth and magic link callbacks:
// app/auth/callback/route.ts
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const code = searchParams.get('code')
if (code) {
const supabase = createClient()
await supabase.auth.exchangeCodeForSession(code)
}
return NextResponse.redirect(new URL('/dashboard', request.url))
}Protecting Routes with Middleware
// middleware.ts
import { createServerClient } from '@supabase/ssr'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(request: NextRequest) {
const response = NextResponse.next()
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: any) {
response.cookies.set({ name, value, ...options })
},
remove(name: string, options: any) {
response.cookies.set({ name, value: '', ...options })
},
},
}
)
const { data: { session } } = await supabase.auth.getSession()
// Redirect to login if not authenticated
if (!session && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return response
}
export const config = {
matcher: ['/dashboard/:path*']
}Row Level Security (RLS)
RLS policies ensure users can only access their own data. Here's an example for a profiles table:
-- Enable RLS
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
-- Users can read their own profile
CREATE POLICY "Users can view own profile"
ON profiles FOR SELECT
USING (auth.uid() = id);
-- Users can update their own profile
CREATE POLICY "Users can update own profile"
ON profiles FOR UPDATE
USING (auth.uid() = id);Getting the Current User
In Server Components
import { createClient } from '@/lib/supabase/server'
export default async function Dashboard() {
const supabase = createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
redirect('/login')
}
return <div>Welcome, {user.email}</div>
}In Client Components
'use client'
import { createClient } from '@/lib/supabase/client'
import { useEffect, useState } from 'react'
export default function Profile() {
const [user, setUser] = useState(null)
const supabase = createClient()
useEffect(() => {
supabase.auth.getUser().then(({ data: { user } }) => {
setUser(user)
})
}, [])
return <div>{user?.email}</div>
}Sign Out
async function handleSignOut() {
const supabase = createClient()
await supabase.auth.signOut()
window.location.href = '/login'
}Best Practices
- Always use HTTPS in production
- Enable RLS on all tables with user data
- Validate email before allowing full access
- Use strong password requirements
- Implement rate limiting on auth endpoints
- Handle errors gracefully with user-friendly messages
- Store sessions securely (Supabase handles this)
Common Pitfalls
1. Using Browser Client in Server Components
Always use the server client for Server Components and the browser client for Client Components.
2. Forgetting to Enable RLS
Without RLS, users can access all data. Always enable and test RLS policies.
3. Not Handling Auth State Changes
Listen for auth state changes to update your UI in real-time:
supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_IN') {
// User signed in
} else if (event === 'SIGNED_OUT') {
// User signed out
}
})Conclusion
Supabase provides a robust, secure authentication system that's easy to implement. With built-in support for multiple auth methods, Row Level Security, and seamless Next.js integration, it's an excellent choice for modern web applications.
Need help implementing Supabase?
We specialize in building secure, scalable applications with Supabase.Contact us to discuss your project.