Cookie auth hilang di Route Handler production biasanya bukan bug acak. Polanya sering sama: di lokal semuanya berjalan normal, tetapi setelah deploy user tiba-tiba sering logout, request ke endpoint internal mulai mengembalikan 401 Unauthorized, dan masalah hanya muncul di environment tertentu seperti staging atau production. Dalam banyak kasus, penyebab utamanya ada pada atribut cookie, perbedaan HTTP vs HTTPS, domain yang tidak cocok, atau cara cookie dibaca dan diteruskan di runtime yang berbeda.
Artikel ini membahas studi kasus debugging backend Next.js untuk kasus tersebut. Fokusnya bukan pada UI login, tetapi pada alur request-response di server: bagaimana cookie diset, kapan browser mengirimkannya, bagaimana Route Handler membacanya, dan kenapa perilakunya bisa berbeda antara lokal dan production.
Gejala yang Muncul di Production
Kasus ini biasanya terlihat dari kombinasi gejala berikut:
- User berhasil login, tetapi beberapa menit kemudian terlihat seperti logout sendiri.
- Request ke endpoint internal seperti
/api/me,/api/session, atau proxy ke backend lain mulai mengembalikan401. - Bug sulit direproduksi di lokal, terutama jika lokal berjalan di
http://localhost. - Masalah hanya muncul pada domain tertentu, subdomain tertentu, atau saat request melewati CDN / reverse proxy.
- Di browser DevTools, cookie terlihat ada, tetapi tidak selalu ikut terkirim pada request yang diharapkan.
Jika gejalanya seperti ini, jangan langsung menyimpulkan bahwa token rusak atau session store bermasalah. Sering kali token valid, tetapi cookie tidak pernah terkirim ke Route Handler yang membutuhkannya.
Studi Kasus Singkat: Login Berhasil, Endpoint Internal 401
Misalkan arsitekturnya seperti ini:
- Aplikasi Next.js memiliki Route Handler di
/api/auth/loginuntuk membuat session cookie. - Route Handler lain, misalnya
/api/profile, membaca cookie tersebut untuk memverifikasi user. - Dalam beberapa kasus, Route Handler Next.js juga meneruskan request ke backend internal lain.
Di lokal, alurnya tampak benar:
- User login.
- Server mengirim
Set-Cookie. - Browser menyimpan cookie.
- Request berikutnya ke Route Handler membawa header
Cookie. - Server membaca session dan mengembalikan data user.
Namun di production, langkah ketiga atau keempat sering gagal. Cookie mungkin tidak disimpan, hanya tersimpan untuk path tertentu, tidak cocok dengan domain aktif, atau tidak ikut dikirim karena atribut keamanan browser.
Root Cause Teknis yang Paling Sering
1. Atribut Secure tidak sesuai dengan HTTP/HTTPS
Cookie dengan atribut Secure hanya akan dikirim melalui HTTPS. Ini benar dan memang diharapkan untuk production. Masalah muncul ketika:
- Aplikasi mengira koneksi masih HTTP karena konfigurasi proxy tidak benar.
- Environment staging memakai HTTP biasa, tetapi cookie tetap diset dengan
Secure. - Di lokal cookie diset tanpa
Secure, lalu di production berubah perilaku karena domain dan skema berubah.
Akibatnya, browser bisa menolak menyimpan cookie atau menyimpannya tetapi tidak mengirimkannya pada request tertentu.
2. Nilai SameSite tidak cocok dengan pola request
SameSite menentukan kapan cookie boleh ikut pada request lintas origin atau lintas site. Salah konfigurasi di sini bisa membuat login tampak berhasil, tetapi request berikutnya tidak membawa cookie.
SameSite=Laxcukup aman untuk banyak kasus same-site biasa.SameSite=Nonedibutuhkan untuk beberapa skenario cross-site, tetapi harus disertaiSecure.SameSite=Strictdapat terlalu ketat dan membuat alur tertentu gagal.
Masalah ini sering muncul jika frontend, domain auth, dan API tidak benar-benar berada dalam konteks site yang sama menurut browser.
3. domain cookie salah
Cookie sangat sensitif terhadap domain. Contoh kesalahan umum:
- Cookie diset untuk
localhostlalu dibawa mentah ke production. - Cookie diset untuk
api.example.com, tetapi request yang perlu autentikasi datang keapp.example.com. - Cookie diset dengan domain yang terlalu spesifik sehingga tidak tersedia pada subdomain lain yang sebenarnya dipakai.
Jika domain tidak cocok, browser tidak akan mengirim cookie ke host target, dan Route Handler akan melihat request sebagai tidak terautentikasi.
4. path cookie terlalu sempit
Jika cookie dibuat dengan path yang terlalu spesifik, misalnya hanya /api/auth, maka cookie itu tidak akan ikut ke endpoint seperti /api/profile. Ini bug yang sering luput karena login sendiri tetap berhasil.
Untuk session utama, path=/ biasanya pilihan paling aman kecuali ada alasan kuat untuk membatasi ruang lingkupnya.
5. Cookie dibaca di runtime yang berbeda dari asumsi kode
Di Next.js, perilaku Route Handler bisa dipengaruhi oleh lingkungan eksekusi. Jika sebagian endpoint berjalan dalam runtime yang berbeda, atau request diproksikan ulang tanpa meneruskan header yang diperlukan, hasilnya bisa membingungkan:
- Cookie ada di request awal, tetapi hilang ketika request diteruskan ke service internal.
- Kode membaca cookie dari helper tertentu, tetapi request sebenarnya datang dari jalur yang berbeda.
- Header
Cookietidak diteruskan saat melakukanfetchserver-to-server.
Ini bukan masalah browser lagi, melainkan masalah alur backend.
6. Request internal dari server tidak otomatis membawa cookie user
Ini salah satu penyebab paling umum. Misalnya Route Handler menerima request user yang sudah punya cookie, lalu di dalam handler dilakukan fetch ke endpoint internal lain. Banyak developer mengira cookie user otomatis ikut. Padahal pada request server-to-server, Anda sering harus meneruskan header Cookie secara eksplisit.
Jika tidak diteruskan, endpoint internal kedua akan menganggap request anonim dan mengembalikan 401.
Langkah Investigasi yang Terstruktur
Saat debugging, jangan mulai dari asumsi. Verifikasi rantai berikut secara sistematis:
- Apakah response login benar-benar mengirim
Set-Cookieyang sesuai? - Apakah browser benar-benar menyimpan cookie itu?
- Apakah request berikutnya mengirim header
Cookie? - Apakah Route Handler menerima dan membaca cookie yang sama?
- Jika handler memanggil service lain, apakah cookie diteruskan?
Periksa response login
Gunakan DevTools browser tab Network atau curl untuk melihat header response dari endpoint login.
curl -i -X POST https://app.example.com/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"secret"}'Hal yang perlu dicek:
- Apakah ada header
Set-Cookie? - Apakah nama cookie sesuai harapan?
- Apakah atribut
HttpOnly,Secure,SameSite,Domain, danPathbenar? - Apakah expiry atau
Max-Agemasuk akal?
Periksa apakah browser menyimpan cookie
Di DevTools, lihat tab Application/Storage pada domain yang aktif. Jika response mengirim Set-Cookie tetapi cookie tidak muncul, browser kemungkinan menolaknya. Penyebabnya biasanya kombinasi atribut yang tidak valid, misalnya SameSite=None tanpa Secure, atau domain yang tidak valid untuk host aktif.
Periksa request berikutnya
Lalu lihat request ke endpoint yang mengembalikan 401. Pastikan header Cookie memang terkirim. Ini langkah penting karena banyak kasus berhenti di sini: cookie tersimpan, tetapi tidak ikut pada request tertentu.
Jika cookie tidak terkirim, fokus pada domain, path, SameSite, dan konteks request.
Tambahkan logging terarah di Route Handler
Untuk sementara, tambahkan logging minimal di sisi server. Jangan log seluruh token rahasia ke production log. Cukup log metadata yang aman, misalnya nama cookie yang ada, host, path, dan apakah header Cookie hadir.
import { cookies, headers } from 'next/headers';
import { NextResponse } from 'next/server';
export async function GET() {
const cookieStore = await cookies();
const headerStore = await headers();
const session = cookieStore.get('session');
const host = headerStore.get('host');
const cookieHeaderPresent = !!headerStore.get('cookie');
console.log('[auth-debug]', {
host,
cookieHeaderPresent,
hasSessionCookie: !!session,
cookieNames: cookieStore.getAll().map((c) => c.name),
});
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
return NextResponse.json({ ok: true });
}Dengan log seperti ini, Anda bisa membedakan apakah masalahnya ada di browser, di edge/proxy, atau di kode pembacaan cookie.
Verifikasi request yang diteruskan antar-service
Jika handler memanggil endpoint internal lain, log header request keluar. Sekali lagi, jangan log isi token penuh. Verifikasi apakah header Cookie atau header auth pengganti benar-benar diteruskan.
import { headers } from 'next/headers';
export async function GET() {
const incomingHeaders = await headers();
const cookieHeader = incomingHeaders.get('cookie') || '';
const res = await fetch('https://internal.example.com/session/validate', {
method: 'GET',
headers: {
Cookie: cookieHeader,
},
cache: 'no-store',
});
// ...
}Tanpa penerusan ini, endpoint internal tidak punya konteks autentikasi user.
Contoh Konfigurasi yang Salah dan yang Benar
Contoh salah: domain dan path terlalu sempit
response.cookies.set('session', token, {
httpOnly: true,
secure: true,
sameSite: 'lax',
domain: 'api.example.com',
path: '/api/auth',
});Masalah dari konfigurasi ini:
- Cookie hanya berlaku untuk
api.example.com, padahal aplikasi mungkin berjalan diapp.example.com. - Cookie hanya berlaku untuk path
/api/auth, sehingga request ke endpoint lain tidak akan membawanya.
Contoh lebih tepat untuk session aplikasi utama
response.cookies.set('session', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
// domain diisi hanya jika benar-benar perlu lintas subdomain
// domain: '.example.com',
maxAge: 60 * 60 * 24 * 7,
});Kenapa ini lebih aman:
path=/memastikan cookie tersedia untuk seluruh aplikasi.securehanya aktif di production HTTPS.sameSite=laxcocok untuk banyak skenario same-site standar.domaintidak dipaksakan jika tidak perlu, sehingga mengurangi risiko salah scope.
Catatan: Jangan menambahkan
domainhanya karena terlihat lebih “lengkap”. Jika aplikasi tidak butuh berbagi cookie antar-subdomain, sering kali lebih aman membiarkannya default ke host saat ini.
Contoh salah: SameSite=None tanpa Secure
response.cookies.set('session', token, {
httpOnly: true,
secure: false,
sameSite: 'none',
path: '/',
});Konfigurasi ini bermasalah karena browser modern mensyaratkan Secure ketika memakai SameSite=None.
Contoh benar untuk skenario yang memang butuh cross-site
response.cookies.set('session', token, {
httpOnly: true,
secure: true,
sameSite: 'none',
path: '/',
});Gunakan ini hanya jika Anda benar-benar punya kebutuhan cross-site. Jika tidak, Lax biasanya lebih sederhana dan aman.
Perbaikan Kode pada Route Handler
Berikut contoh Route Handler login dan endpoint proteksi yang lebih konsisten.
Login: set cookie dengan atribut yang sesuai environment
import { NextResponse } from 'next/server';
export async function POST(req) {
const { email, password } = await req.json();
// Ganti dengan verifikasi kredensial yang sebenarnya
const isValidUser = email && password;
if (!isValidUser) {
return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 });
}
const token = 'signed-session-token';
const response = NextResponse.json({ ok: true });
response.cookies.set('session', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
maxAge: 60 * 60 * 24,
});
return response;
}Protected route: baca cookie dari request aktif
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
export async function GET() {
const cookieStore = await cookies();
const session = cookieStore.get('session')?.value;
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Validasi token/session sesuai backend Anda
return NextResponse.json({ user: { id: '123', email: 'user@example.com' } });
}Jika perlu meneruskan autentikasi ke endpoint internal
import { headers } from 'next/headers';
import { NextResponse } from 'next/server';
export async function GET() {
const headerStore = await headers();
const cookieHeader = headerStore.get('cookie');
const upstream = await fetch('https://internal.example.com/api/me', {
headers: cookieHeader ? { Cookie: cookieHeader } : {},
cache: 'no-store',
});
if (upstream.status === 401) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const data = await upstream.json();
return NextResponse.json(data);
}Inti perbaikannya ada dua:
- Pastikan cookie diset dengan scope yang benar.
- Pastikan cookie dibaca dan diteruskan secara eksplisit jika alurnya melibatkan request server-to-server.
Checklist Debugging Deployment
Sebelum menyimpulkan bug ada di Next.js, cek poin-poin ini:
- HTTPS aktif di production dan sesuai dengan asumsi atribut
Secure. - Host dan subdomain konsisten antara URL login, URL aplikasi, dan endpoint yang butuh session.
- Cookie path tidak terlalu sempit.
- SameSite sesuai dengan konteks request nyata.
- Proxy / load balancer / CDN tidak memodifikasi header penting secara tak terduga.
- Redirect antar-domain tidak mengubah origin sehingga cookie tak lagi cocok.
- Server-side fetch yang bergantung pada session meneruskan header auth yang diperlukan.
- Environment variable untuk base URL tidak menyebabkan request internal pergi ke host yang berbeda dari cookie scope.
Kesalahan Umum yang Sering Menyesatkan
Menganggap lokal mewakili production
localhost bukan simulasi sempurna untuk domain production. Perbedaan skema, subdomain, proxy, dan kebijakan browser membuat bug cookie sering tersembunyi di lokal.
Mengandalkan pembacaan cookie tanpa melihat header mentah
Helper framework memudahkan akses cookie, tetapi saat debugging Anda tetap perlu melihat header request/response secara langsung. Dari sana biasanya masalah cepat terlihat.
Menyet domain cookie terlalu cepat
Banyak tim langsung menambahkan domain=.example.com tanpa benar-benar perlu. Ini bisa membantu jika Anda memang berbagi session antar-subdomain, tetapi juga bisa menambah kompleksitas dan risiko salah konfigurasi.
Tidak membedakan request browser dan request server
Browser punya mekanisme otomatis untuk mengirim cookie. Request dari server tidak selalu mengikuti aturan itu secara otomatis. Jika Route Handler memanggil endpoint lain, Anda harus memperlakukan itu sebagai request baru dengan header baru.
Cara Mencegah Regresi
Buat uji integrasi untuk skenario login dan endpoint proteksi
Minimal pastikan skenario berikut diuji pada environment yang menyerupai production:
- Login mengembalikan
Set-Cookiedengan atribut yang benar. - Request kedua ke endpoint proteksi berhasil dengan cookie tersebut.
- Route Handler yang memanggil service internal tetap mempertahankan konteks auth.
Log metadata auth yang aman
Tambahkan logging yang tidak membocorkan rahasia, misalnya:
- apakah header
Cookiehadir, - nama cookie yang terbaca,
- host dan path request,
- status response dari upstream auth service.
Ini sangat membantu saat bug hanya muncul di production.
Dokumentasikan kebijakan cookie per environment
Tentukan dengan jelas:
- kapan
secureharus aktif, - apakah aplikasi membutuhkan lintas subdomain,
- nilai
SameSiteyang dipakai dan alasannya, - base URL mana yang boleh dipakai untuk internal fetch.
Tanpa dokumentasi seperti ini, bug lama mudah kembali saat ada perubahan domain, proxy, atau strategi deploy.
Kesimpulan
Kasus Debugging Next.js: Cookie Auth Hilang di Route Handler Production hampir selalu bisa dipecahkan jika Anda memeriksa alur autentikasi dari level HTTP, bukan hanya dari kode bisnis. Fokuskan investigasi pada empat hal: bagaimana cookie diset, apakah browser menyimpannya, apakah cookie ikut pada request yang gagal, dan apakah Route Handler meneruskan konteks auth saat memanggil service lain.
Dalam studi kasus seperti ini, akar masalah paling sering ada pada kombinasi Secure, SameSite, domain, path, perbedaan HTTP/HTTPS, atau asumsi keliru bahwa request internal otomatis membawa cookie user. Begitu rantai ini diverifikasi satu per satu, bug yang sebelumnya terasa acak biasanya berubah menjadi masalah konfigurasi yang sangat konkret dan bisa diperbaiki permanen.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!