- Learn
- Stack Essentials
- Supabase
- Authentication
Intermediate20 min1 prerequisite
Implement user authentication with Supabase Auth including signup, login, OAuth, and session management.
Authentication
Supabase Auth provides complete authentication with email, social logins, and session management built-in.
Auth Methods Overview
| Method | Use Case |
|---|---|
| Email/Password | Traditional signup |
| Magic Link | Passwordless email |
| OAuth | Google, GitHub, etc. |
| Phone/OTP | Mobile verification |
Email/Password Auth
Sign Up
Terminal
'use client'
import { createClient } from '@/lib/supabase/client'
async function signUp(email: string, password: string) {
const supabase = createClient()
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
// Optional: redirect after email confirmation
emailRedirectTo: `${window.location.origin}/auth/callback`,
// Optional: additional user metadata
data: {
name: 'John Doe',
}
}
})
if (error) throw error
return data
}
Sign In
Terminal
async function signIn(email: string, password: string) {
const supabase = createClient()
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) throw error
return data
}
Sign Out
Terminal
async function signOut() {
const supabase = createClient()
const { error } = await supabase.auth.signOut()
if (error) throw error
}
Magic Link (Passwordless)
Terminal
async function signInWithMagicLink(email: string) {
const supabase = createClient()
const { error } = await supabase.auth.signInWithOtp({
email,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`,
}
})
if (error) throw error
// User receives email with login link
}
OAuth (Social Login)
Configure Provider
- Go to Authentication → Providers
- Enable desired provider (Google, GitHub, etc.)
- Add Client ID and Secret from provider
Google Sign In
Terminal
async function signInWithGoogle() {
const supabase = createClient()
const { error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
queryParams: {
access_type: 'offline',
prompt: 'consent',
}
}
})
if (error) throw error
}
GitHub Sign In
Terminal
async function signInWithGitHub() {
const supabase = createClient()
const { error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
scopes: 'read:user user:email'
}
})
if (error) throw error
}
OAuth Callback Handler
Terminal
// 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 = await createClient()
await supabase.auth.exchangeCodeForSession(code)
}
// Redirect to dashboard after auth
return NextResponse.redirect(new URL('/dashboard', requestUrl.origin))
}
Session Management
Get Current User
Terminal
// Client Component
'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 [loading, setLoading] = useState(true)
useEffect(() => {
const supabase = createClient()
// Get initial user
supabase.auth.getUser().then(({ data: { user } }) => {
setUser(user)
setLoading(false)
})
// Listen for auth changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
setUser(session?.user ?? null)
}
)
return () => subscription.unsubscribe()
}, [])
return { user, loading }
}
Terminal
// Server Component
import { createClient } from '@/lib/supabase/server'
export default async function Dashboard() {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
redirect('/login')
}
return <div>Welcome, {user.email}</div>
}
Get Session
Terminal
// Get full session (includes access token)
const { data: { session } } = await supabase.auth.getSession()
// Access token for external APIs
const accessToken = session?.access_token
Listen for Auth Changes
Terminal
'use client'
import { createClient } from '@/lib/supabase/client'
import { useEffect } from 'react'
import { useRouter } from 'next/navigation'
export function AuthListener() {
const router = useRouter()
const supabase = createClient()
useEffect(() => {
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
if (event === 'SIGNED_IN') {
router.push('/dashboard')
}
if (event === 'SIGNED_OUT') {
router.push('/login')
}
}
)
return () => subscription.unsubscribe()
}, [router, supabase])
return null
}
Password Management
Password Reset
Terminal
// Request reset email
async function requestPasswordReset(email: string) {
const supabase = createClient()
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/auth/reset-password`,
})
if (error) throw error
}
// Update password (on reset page)
async function updatePassword(newPassword: string) {
const supabase = createClient()
const { error } = await supabase.auth.updateUser({
password: newPassword
})
if (error) throw error
}
Change Password (Logged In)
Terminal
async function changePassword(newPassword: string) {
const supabase = createClient()
const { error } = await supabase.auth.updateUser({
password: newPassword
})
if (error) throw error
}
User Metadata
Update User Profile
Terminal
async function updateProfile(name: string, avatarUrl?: string) {
const supabase = createClient()
const { error } = await supabase.auth.updateUser({
data: {
name,
avatar_url: avatarUrl,
}
})
if (error) throw error
}
Access Metadata
Terminal
const { data: { user } } = await supabase.auth.getUser()
// User metadata from signup
const name = user?.user_metadata?.name
// Provider data (for OAuth)
const avatarUrl = user?.user_metadata?.avatar_url
Protected Routes
Middleware Protection
Terminal
// middleware.ts
import { createClient } from '@/lib/supabase/middleware'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const publicRoutes = ['/', '/login', '/signup', '/auth/callback']
export async function middleware(request: NextRequest) {
const { supabase, response } = createClient(request)
const { data: { session } } = await supabase.auth.getSession()
const isPublicRoute = publicRoutes.includes(request.nextUrl.pathname)
if (!session && !isPublicRoute) {
return NextResponse.redirect(new URL('/login', request.url))
}
if (session && (request.nextUrl.pathname === '/login')) {
return NextResponse.redirect(new URL('/dashboard', request.url))
}
return response
}
Server Component Protection
Terminal
// app/dashboard/page.tsx
import { createClient } from '@/lib/supabase/server'
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
redirect('/login')
}
// Fetch user-specific data
const { data: posts } = await supabase
.from('posts')
.select('*')
.eq('user_id', user.id)
return <Dashboard posts={posts} user={user} />
}
Complete Auth Forms
Login Form
Terminal
'use client'
import { useState } from 'react'
import { createClient } from '@/lib/supabase/client'
import { useRouter } from 'next/navigation'
export function LoginForm() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(false)
const router = useRouter()
const supabase = createClient()
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setError(null)
setLoading(true)
const { error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) {
setError(error.message)
setLoading(false)
return
}
router.push('/dashboard')
router.refresh()
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="p-3 bg-red-100 text-red-700 rounded">
{error}
</div>
)}
<div>
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div>
<label htmlFor="password">Password</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit" disabled={loading}>
{loading ? 'Signing in...' : 'Sign In'}
</button>
</form>
)
}
Signup Form
Terminal
'use client'
import { useState } from 'react'
import { createClient } from '@/lib/supabase/client'
export function SignupForm() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [message, setMessage] = useState<string | null>(null)
const [loading, setLoading] = useState(false)
const supabase = createClient()
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setMessage(null)
setLoading(true)
const { error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`,
}
})
setLoading(false)
if (error) {
setMessage(error.message)
return
}
setMessage('Check your email for the confirmation link!')
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
{message && (
<div className="p-3 bg-blue-100 text-blue-700 rounded">
{message}
</div>
)}
<div>
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div>
<label htmlFor="password">Password</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
minLength={6}
required
/>
</div>
<button type="submit" disabled={loading}>
{loading ? 'Creating account...' : 'Sign Up'}
</button>
</form>
)
}
Summary
- Email/Password:
signUp(),signInWithPassword(),signOut() - Magic Link:
signInWithOtp({ email }) - OAuth:
signInWithOAuth({ provider }) - Session:
getUser(),getSession(),onAuthStateChange() - Password:
resetPasswordForEmail(),updateUser({ password }) - Protection: Use middleware or server-side checks
Next Steps
Secure your data with Row Level Security (RLS) policies.
Mark this lesson as complete to track your progress