Supabase Auth + Next.js 16: Panduan Lengkap Authentication 2026
Authentication adalah bagian tersulit dalam web development. Dengan Supabase Auth dan Next.js 16, proses ini jadi jauh lebih mudah — tanpa korbankan keamanan. Ini implementasi lengkapnya.
Muhamad Putra Aulia Hidayat
Supabase Auth + Next.js 16: Authentication yang Aman dan Mudah
Membangun auth dari scratch itu ribet dan berisiko. Supabase Auth memberikan solusi yang production-ready, secure, dan tetap fleksibel. Ini cara implementasinya di Next.js 16.
Yang Bisa Dilakukan Supabase Auth
- Email + Password
- Magic Link (passwordless)
- OAuth: Google, GitHub, Discord, Twitter, dll
- Phone/SMS OTP
- Multi-Factor Authentication (MFA)
- Row Level Security (data otomatis terisolasi per user)
Setup
npm install @supabase/supabase-js @supabase/ssr
// lib/supabase/client.ts - Browser client
import { createBrowserClient } from "@supabase/ssr"
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
// lib/supabase/server.ts - Server client
import { createServerClient } from "@supabase/ssr"
import { cookies } from "next/headers"
export async function createClient() {
const cookieStore = await cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll: () => cookieStore.getAll(),
setAll: (cookiesToSet) => {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
}
}
}
)
}
Middleware untuk Protected Routes
// middleware.ts
import { createServerClient } from "@supabase/ssr"
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
export async function middleware(request: NextRequest) {
let supabaseResponse = NextResponse.next({ request })
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll: () => request.cookies.getAll(),
setAll: (cookiesToSet) => {
cookiesToSet.forEach(({ name, value, options }) => {
supabaseResponse.cookies.set(name, value, options)
})
}
}
}
)
const { data: { user } } = await supabase.auth.getUser()
const isAuthPage = request.nextUrl.pathname.startsWith("/login") ||
request.nextUrl.pathname.startsWith("/register")
const isProtected = request.nextUrl.pathname.startsWith("/dashboard")
if (!user && isProtected) {
return NextResponse.redirect(new URL("/login", request.url))
}
if (user && isAuthPage) {
return NextResponse.redirect(new URL("/dashboard", request.url))
}
return supabaseResponse
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
}
Login Page
// app/login/page.tsx
"use client"
import { useState } from "react"
import { createClient } from "@/lib/supabase/client"
import { useRouter } from "next/navigation"
export default function LoginPage() {
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const router = useRouter()
const supabase = createClient()
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
setError(null)
const { error } = await supabase.auth.signInWithPassword({ email, password })
if (error) {
setError(error.message)
setLoading(false)
return
}
router.push("/dashboard")
router.refresh()
}
const handleGoogleLogin = async () => {
await supabase.auth.signInWithOAuth({
provider: "google",
options: { redirectTo: `${location.origin}/auth/callback` }
})
}
return (
<form onSubmit={handleLogin}>
<input type="email" value={email} onChange={e => setEmail(e.target.value)} />
<input type="password" value={password} onChange={e => setPassword(e.target.value)} />
{error && <p className="text-red-500">{error}</p>}
<button type="submit" disabled={loading}>Masuk</button>
<button type="button" onClick={handleGoogleLogin}>Login dengan Google</button>
</form>
)
}
Row Level Security
Ini yang membuat Supabase sangat aman — data user secara otomatis terisolasi:
-- Aktifkan RLS
ALTER TABLE notes ENABLE ROW LEVEL SECURITY;
-- User hanya bisa lihat data miliknya
CREATE POLICY "Users see own notes"
ON notes FOR SELECT
USING (user_id = auth.uid());
-- User hanya bisa insert data miliknya
CREATE POLICY "Users insert own notes"
ON notes FOR INSERT
WITH CHECK (user_id = auth.uid());
-- User hanya bisa update data miliknya
CREATE POLICY "Users update own notes"
ON notes FOR UPDATE
USING (user_id = auth.uid());
Dengan RLS aktif, bahkan kalau ada bug di kode API Anda, user tidak bisa mengakses data orang lain.
Ambil Data User di Server Component
// app/dashboard/page.tsx
import { createClient } from "@/lib/supabase/server"
import { redirect } from "next/navigation"
export default async function Dashboard() {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) redirect("/login")
const { data: notes } = await supabase
.from("notes")
.select("*")
.order("created_at", { ascending: false })
// RLS otomatis filter hanya notes milik user ini
return (
<div>
<h1>Halo, {user.email}</h1>
{notes?.map(note => <p key={note.id}>{note.content}</p>)}
</div>
)
}
Dengan setup ini, Anda punya authentication yang production-ready dalam hitungan jam, bukan hari.
Newsletter Digital Uptime
Tips teknologi & bisnis mingguan
Bergabung dengan 2,500+ subscriber yang mendapatkan insight teknologi, tutorial development, dan tips bisnis digital langsung ke inbox mereka setiap minggu.