Kasus yang sering membingungkan pada Nuxt adalah halaman login atau dashboard mengembalikan 401 hanya saat refresh, sementara navigasi melalui client berjalan normal. Penyebab utamanya sering bukan sesi yang benar-benar hilang, melainkan request internal saat SSR tidak membawa cookie autentikasi pengguna.
Ketika kode SSR memanggil $fetch('/api/me'), request tersebut dijalankan oleh proses server Nitro. Server tidak memiliki perilaku browser yang otomatis mengirim ulang cookie dari request halaman pengguna. Gunakan useFetch atau useRequestFetch, atau teruskan header yang diperlukan secara eksplisit dan aman.
Studi kasus: 401 saat refresh, normal setelah navigasi client
Misalkan aplikasi memiliki endpoint /api/me yang membaca cookie sesi. Route middleware atau halaman memuat pengguna aktif seperti berikut:
const { data: user, error } = await useAsyncData(
'current-user',
() => $fetch('/api/me')
)
Gejalanya:
- Pengguna sudah login dan cookie sesi terlihat di browser.
- Refresh langsung ke
/dashboardmenghasilkan 401 atau redirect ke/login. - Setelah halaman terbuka, navigasi ke route lain melalui client berhasil.
- Endpoint yang sama dapat diakses normal dari browser setelah aplikasi selesai dimuat.
Timeline request yang menjelaskan gejala
- Browser mengirim request dokumen ke Nuxt, termasuk cookie sesi.
- Nitro menjalankan middleware atau
useAsyncDatadi server untuk merender halaman. - Kode memanggil
$fetch('/api/me')dari proses server. - Request internal tersebut tidak otomatis mewarisi cookie dari request browser.
- Endpoint
/api/metidak menemukan kredensial dan mengembalikan 401. - Saat navigasi client,
$fetchberjalan di browser. Browser kemudian mengirim cookie untuk request same-origin, sehingga request berhasil.
Dengan demikian, perbedaan SSR dan client navigation merupakan petunjuk penting. Cookie dapat hadir pada request browser ke Nuxt, tetapi belum tentu diteruskan dari Nuxt ke API internal.
Root cause: konteks request tidak otomatis diwariskan oleh $fetch
$fetch adalah utilitas HTTP. Saat dipanggil di browser, browser mengelola cookie sesuai aturan cookie dan origin. Saat dipanggil di server, tidak ada browser yang mengisi header Cookie secara otomatis. Request server-to-server harus diberi konteks request pengguna secara eksplisit.
Opsi credentials: 'include' tidak menyelesaikan masalah ini dengan sendirinya. Opsi tersebut relevan untuk aturan credential pada request fetch, tetapi tidak berarti cookie dari request masuk ke server akan otomatis disalin ke request keluar.
Untuk request relatif dalam aplikasi Nuxt, gunakan:
useFetchuntuk pola umum mengambil data dalam komponen atau middleware. Nuxt dapat memakai konteks request yang sesuai saat SSR.useRequestFetchketika ingin memakai instance fetch yang secara khusus membawa konteks request masuk.- Forwarding header eksplisit ketika membutuhkan kontrol ketat, misalnya hanya meneruskan
cookiedanauthorization.
Perbaikan kode: dari $fetch ke useFetch atau useRequestFetch
Sebelum: $fetch tanpa konteks request
const { data: user, error } = await useAsyncData(
'current-user',
() => $fetch('/api/me')
)
Pola ini dapat berjalan pada client, tetapi bermasalah ketika callback dieksekusi di server dan endpoint membutuhkan cookie pengguna.
Sesudah: gunakan useFetch untuk endpoint relatif
const { data: user, error } = await useFetch('/api/me', {
key: 'current-user'
})
Gunakan pendekatan ini bila kebutuhan Anda hanya mengambil endpoint internal berdasarkan konteks request saat ini. Hindari membungkus useFetch di dalam useAsyncData karena keduanya sudah menangani pola data fetching Nuxt.
Sesudah: gunakan useRequestFetch di dalam useAsyncData
const requestFetch = useRequestFetch()
const { data: user, error } = await useAsyncData(
'current-user',
() => requestFetch('/api/me')
)
Pola ini berguna ketika struktur aplikasi sudah menggunakan useAsyncData atau ketika Anda ingin mengganti implementasi fetch tanpa mengubah pola pengelolaan data.
Contoh middleware autentikasi
export default defineNuxtRouteMiddleware(async () => {
const { data: user, error } = await useFetch('/api/me', {
key: 'current-user'
})
if (error.value?.statusCode === 401 || !user.value) {
return navigateTo('/login')
}
})
Status error aktual tetap perlu disesuaikan dengan cara endpoint dan handler aplikasi membentuk error. Yang penting, middleware memanggil fetch yang membawa konteks request saat SSR.
Forwarding header yang aman
Jika Anda harus tetap memakai $fetch, teruskan hanya header yang memang dibutuhkan. Untuk autentikasi berbasis cookie, biasanya cukup cookie. Jika API juga mendukung token bearer, teruskan authorization sesuai kebutuhan.
const incomingHeaders = useRequestHeaders([
'cookie',
'authorization'
])
const { data: user } = await useAsyncData(
'current-user',
() => $fetch('/api/me', {
headers: import.meta.server
? incomingHeaders
: undefined
})
)
Allowlist header lebih aman daripada menyalin seluruh event.node.req.headers. Header seperti host, content-length, atau header proxy dapat mengubah tujuan request, memicu perilaku yang tidak diharapkan, atau membocorkan metadata. Jangan mencatat nilai cookie maupun token ke log.
Untuk API di domain berbeda, aturan cookie dan CORS perlu dikonfigurasi terpisah. Pastikan backend memang mengizinkan credential, origin yang benar, dan cookie dikirim sesuai kebijakan browser. Untuk endpoint Nitro internal, useRequestFetch biasanya lebih sederhana dan lebih kecil risikonya daripada merakit forwarding manual.
Validasi cookie, SameSite, Secure, dan proxy
Setelah memperbaiki forwarding, periksa juga konfigurasi cookie. Masalah SSR dapat tampak seperti masalah $fetch padahal cookie memang tidak pernah dikirim oleh browser.
- Path dan Domain: pastikan cookie berlaku untuk path dan host yang menerima request halaman serta endpoint API.
- SameSite: nilai yang terlalu ketat dapat mencegah cookie dikirim pada alur lintas situs. Pilih kebijakan berdasarkan arsitektur origin aplikasi, bukan sekadar menyalinnya dari contoh.
- Secure: cookie Secure hanya dikirim melalui HTTPS. Periksa perbedaan lingkungan lokal dan production.
- HttpOnly: cocok untuk cookie sesi karena mencegah JavaScript client membacanya, tetapi cookie tetap dapat dikirim otomatis oleh browser.
- Reverse proxy: jika TLS berhenti di load balancer atau proxy, pastikan aplikasi dan proxy konsisten dalam mendeteksi HTTPS serta meneruskan host dan skema yang relevan.
- Multiple host: jangan mencampur
www, root domain, dan subdomain API tanpa aturan Domain dan CORS yang jelas.
Gunakan DevTools browser untuk memeriksa apakah cookie tersimpan dan terkirim pada request halaman. Di sisi Nitro, periksa header masuk secara aman tanpa mencetak nilai rahasianya.
Timeline investigasi dan log yang perlu ditambahkan
Debugging akan lebih cepat jika setiap tahap request memiliki correlation ID yang sama. Tambahkan log terstruktur di handler halaman atau middleware, wrapper fetch, dan endpoint /api/me.
const event = useRequestEvent()
const requestId = getHeader(event, 'x-request-id') || 'generated-id'
const hasCookie = Boolean(getHeader(event, 'cookie'))
const hasAuthorization = Boolean(getHeader(event, 'authorization'))
console.info({
requestId,
phase: 'auth-check',
runtime: import.meta.server ? 'server' : 'client',
hasCookie,
hasAuthorization
})
Contoh informasi yang berguna untuk dibandingkan:
- request ID dan waktu request halaman dimulai;
- runtime eksekusi: server atau client;
- route halaman dan endpoint tujuan;
- apakah request masuk memiliki cookie atau authorization header, tanpa mencatat nilainya;
- apakah request keluar meneruskan header yang diizinkan;
- status response
/api/medan durasi request; - hasil akhir middleware: lanjut, 401, atau redirect.
Jika request halaman memiliki cookie tetapi log endpoint internal menunjukkan cookie tidak ada, root cause hampir pasti berada pada boundary forwarding. Jika cookie sudah diteruskan tetapi tetap 401, lanjutkan investigasi ke atribut cookie, session store, domain, proxy, atau validasi sesi.
Test regresi
Tambahkan test yang membedakan SSR dari navigasi client. Minimal, gunakan request dengan cookie sesi palsu yang valid di lingkungan test dan verifikasi bahwa halaman terlindungi tidak mengembalikan redirect atau 401 saat dirender dari server.
- Test request SSR ke route terlindungi dengan header
Cookie; pastikan endpoint/api/memenerima konteks autentikasi. - Test request SSR tanpa cookie; pastikan hasilnya tetap 401 atau redirect sesuai kontrak aplikasi.
- Test navigasi client setelah login untuk memastikan perilaku client tetap berjalan.
- Test cookie Secure, SameSite, Domain, dan Path pada environment yang merepresentasikan production.
- Test melalui reverse proxy atau ingress, bukan hanya langsung ke proses Nitro.
Untuk pengujian end-to-end, simpan status login melalui mekanisme test runner, lalu lakukan direct navigation atau reload ke route terlindungi. Kasus reload penting karena memaksa alur SSR yang sering tidak tercakup oleh navigasi client.
Checklist observability dan troubleshooting
- Apakah 401 hanya terjadi pada direct request atau refresh?
- Apakah request browser ke Nuxt membawa cookie yang diharapkan?
- Apakah kode berjalan di server Nitro atau di browser?
- Apakah
$fetchdipakai tanpa forwarding konteks request? - Apakah
useFetchatauuseRequestFetchdapat digunakan untuk endpoint relatif? - Jika forwarding manual dipakai, apakah hanya header yang diperlukan yang diteruskan?
- Apakah log memiliki request ID, runtime, route, status, dan durasi tanpa membocorkan rahasia?
- Apakah atribut cookie dan konfigurasi proxy konsisten dengan HTTPS serta domain production?
- Apakah ada test regresi untuk refresh SSR, tanpa cookie, navigasi client, dan reverse proxy?
Inti masalahnya adalah perbedaan konteks eksekusi: browser otomatis menangani cookie, sedangkan server tidak. Untuk data autentikasi saat SSR Nuxt, jadikan useFetch atau useRequestFetch sebagai pilihan utama. Bila forwarding manual diperlukan, gunakan allowlist header, observability tanpa data sensitif, dan test reload agar bug 401 tidak kembali.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!