nextjsauthmiddlewareMahir4 mnt baca

Proteksi Route dengan Auth

Menggabungkan middleware + session untuk halaman privat.

Strategi berlapis: cepat di depan, pasti di belakang

Melindungi halaman privat butuh dua lapis pertahanan yang saling melengkapi:

  1. Middleware (lapis cepat): mengecek keberadaan cookie session. Murah dan instan, tapi dangkal, ia tidak tahu apakah token di cookie itu asli atau palsu.
  2. Server Component (lapis pasti): memverifikasi token ke database. Lebih lambat, tapi memberi kepastian.

Kenapa tidak cukup salah satu? Middleware berjalan di Edge Runtime yang tidak cocok untuk query database berat, jadi ia tidak bisa memastikan session valid. Sebaliknya, mengandalkan Server Component saja berarti setiap request ke halaman privat tetap me-render dulu sebelum ketahuan tidak berhak, dan API route-mu tetap terbuka tanpa penjaga. Dua lapis menutup kedua celah itu.

Contoh 1: middleware sebagai penjaga gerbang

tsx
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(req: NextRequest) {
  const session = req.cookies.get("session");
  const url = req.nextUrl.clone();

  // Belum login tapi menuju area privat -> lempar ke /login
  if (!session && url.pathname.startsWith("/dashboard")) {
    url.pathname = "/login";
    url.searchParams.set("next", req.nextUrl.pathname); // ingat tujuan awal
    return NextResponse.redirect(url);
  }

  // Sudah login tapi membuka /login -> lempar ke dashboard
  if (session && url.pathname === "/login") {
    url.pathname = "/dashboard";
    return NextResponse.redirect(url);
  }

  return NextResponse.next();
}

export const config = { matcher: ["/dashboard/:path*", "/login"] };

Detail yang sering dilupakan: menyimpan tujuan awal di query ?next=/dashboard/... agar setelah login user kembali ke halaman yang ia tuju, bukan selalu ke halaman depan dashboard. Dan arah sebaliknya juga dijaga: user yang sudah login tidak perlu melihat halaman login lagi.

Contoh 2: verifikasi penuh di Server Component

tsx
// app/dashboard/page.tsx
import { cookies } from "next/headers";
import { redirect } from "next/navigation";

export default async function Dashboard() {
  const token = (await cookies()).get("session")?.value;
  const user = token ? await getUserBySession(token) : null;

  if (!user) redirect("/login"); // token palsu/kedaluwarsa tetap ditolak

  return (
    <main>
      <h1>Halo, {user.name}</h1>
      <p>Ini data privat yang hanya boleh dilihat pemilik akun.</p>
    </main>
  );
}

Di sinilah cookie palsu digagalkan. Middleware meloloskan request karena cookie ada, tapi getUserBySession mencari token itu di database. Token hasil karangan tidak akan ketemu, user jadi null, dan user dilempar ke /login. Inilah alasan lapis kedua tidak boleh dilewatkan.

Pola yang sama berlaku untuk Route Handler yang menyajikan data privat:

ts
// app/api/rahasia/route.ts
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export async function GET() {
  const token = (await cookies()).get("session")?.value;
  const user = token ? await getUserBySession(token) : null;
  if (!user) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  return NextResponse.json({ data: "rahasia milik " + user.name });
}

Ingat: proteksi halaman tanpa memproteksi API-nya sama dengan mengunci pintu depan tapi membiarkan jendela terbuka.

Tantangan

Dua lapis

Implementasikan proteksi dua lapis untuk /dashboard: middleware cek cookie + page verifikasi session dummy. Test: tanpa cookie → /login; dengan cookie palsu → tetap /login (gagal verifikasi).