Pada aplikasi Nuxt.js, masalah cookie auth hilang saat SSR di reverse proxy biasanya muncul dengan pola yang membingungkan: pengguna berhasil login, cookie terlihat ada di browser, request API dari tab browser juga tampak sukses, tetapi saat halaman di-render melalui SSR, backend selalu menganggap user belum terautentikasi.

Dalam banyak kasus, masalahnya bukan pada mekanisme login itu sendiri, melainkan pada rantai request antara browser, reverse proxy, server Nuxt, dan API backend. Cookie bisa tersimpan di browser, tetapi tidak ikut terbawa pada request SSR, atau request SSR dikirim dengan skema/origin yang salah sehingga backend menolak cookie tersebut. Artikel ini membahas studi kasus debugging yang konkret, cara membaca gejala di log, akar masalah yang umum, dan langkah perbaikan yang bisa diterapkan.

Arsitektur singkat dan kenapa masalah ini hanya muncul saat SSR

Alur auth pada aplikasi SSR berbeda dari SPA murni.

Alur di browser

  1. User submit login.
  2. Backend mengirim Set-Cookie.
  3. Browser menyimpan cookie.
  4. Request berikutnya dari browser ke domain yang sesuai akan otomatis membawa header Cookie.

Alur saat SSR

  1. Browser meminta halaman ke server Nuxt.
  2. Server Nuxt melakukan fetch ke API backend untuk mengambil data user/session.
  3. Jika server Nuxt tidak meneruskan cookie dari request masuk, backend melihat request itu sebagai anonim.

Jadi, walaupun browser sudah punya cookie, SSR tidak otomatis memakai cookie itu kecuali server Nuxt benar-benar menerima dan meneruskannya. Di lingkungan reverse proxy, ada lapisan tambahan seperti terminasi TLS, rewrite host, dan header X-Forwarded-Proto yang dapat mengubah perilaku auth.

Gejala yang biasanya terlihat

  • Login sukses, lalu redirect ke dashboard.
  • Jika navigasi dilakukan di client-side, beberapa API terlihat terautentikasi.
  • Saat reload halaman atau membuka URL langsung, SSR gagal mengambil data user.
  • UI SSR menampilkan state guest, lalu setelah hydration atau request client-side, user mendadak terlihat login.
  • Backend mencatat request SSR tanpa cookie session atau menganggap cookie invalid.

Perbedaan ini adalah petunjuk penting: client-side request dan SSR request tidak membawa konteks auth yang sama.

Studi kasus: local aman, production gagal di belakang reverse proxy

Misalkan arsitekturnya seperti ini:

  • Browser mengakses https://app.example.com
  • Reverse proxy menerima HTTPS lalu meneruskan ke Nuxt melalui HTTP internal
  • Nuxt server melakukan request ke API di http://api-internal:8080 atau ke domain publik API
  • Backend API memvalidasi cookie session

Di lokal, developer sering menjalankan semuanya dalam satu origin sederhana, misalnya http://localhost, tanpa TLS, tanpa reverse proxy, dan tanpa perbedaan host yang nyata. Karena itu banyak masalah cookie tidak terlihat. Di production, atribut cookie dan header forward mulai berpengaruh.

Contoh gejala nyata

Setelah login:

  • DevTools browser menunjukkan cookie session tersimpan.
  • Request /api/me dari browser membawa cookie.
  • Namun log backend untuk request dari SSR menunjukkan header Cookie kosong.

Atau variasi lain:

  • Cookie ada, tetapi hanya berlaku untuk domain tertentu.
  • Browser tidak mengirim cookie pada request tertentu karena atribut Secure, SameSite, atau Domain tidak cocok.
  • Nuxt membangun URL backend dengan skema http padahal dari sudut pandang user aplikasinya berjalan di https.

Langkah reproduksi yang terarah

Jangan mulai dari asumsi. Reproduksi masalah dengan langkah yang memisahkan browser, proxy, dan SSR.

1. Login dan inspeksi Set-Cookie

Di tab Network browser, lihat response login dan periksa header Set-Cookie. Catat:

  • Nama cookie session
  • Domain
  • Path
  • HttpOnly
  • Secure
  • SameSite
  • Expiry/Max-Age

Pertanyaan penting:

  • Apakah domain cookie cocok dengan host yang diakses user?
  • Apakah cookie bertanda Secure tetapi ada hop yang dianggap HTTP?
  • Apakah SameSite terlalu ketat untuk alur yang melibatkan origin berbeda?

2. Reload halaman yang butuh SSR

Setelah login, lakukan hard reload pada halaman yang memerlukan data user dari SSR. Jika halaman awal kembali menjadi guest, itu mengindikasikan SSR tidak menerima auth context yang sama dengan browser.

3. Bandingkan request browser vs request SSR

Anda perlu log minimal di dua sisi:

  • Header request yang masuk ke server Nuxt
  • Header request yang keluar dari Nuxt menuju backend API

Tujuannya adalah memastikan apakah cookie benar-benar diterima oleh Nuxt dan apakah cookie tersebut diteruskan ke backend.

Cara membaca log request/response dengan tepat

Log di server Nuxt

Tambahkan logging sementara pada handler server, middleware, atau wrapper fetch yang dipakai SSR. Fokus pada header berikut:

  • host
  • x-forwarded-host
  • x-forwarded-proto
  • x-forwarded-for
  • cookie
  • origin
  • referer
// Contoh pseudocode/logging di layer server Nuxt atau Nitro
export default defineEventHandler(async (event) => {
  const headers = getRequestHeaders(event)

  console.log('incoming host=', headers.host)
  console.log('incoming x-forwarded-proto=', headers['x-forwarded-proto'])
  console.log('incoming origin=', headers.origin)
  console.log('incoming cookie=', headers.cookie)

  return { ok: true }
})

Kalau incoming cookie kosong saat request halaman masuk ke Nuxt, masalahnya ada sebelum atau di browser/proxy. Kalau header cookie ada pada request masuk ke Nuxt tetapi hilang pada request keluar menuju API, masalahnya ada di layer SSR fetch atau proxying internal.

Log request keluar dari Nuxt ke API

Pada wrapper fetch SSR, log URL tujuan dan header yang benar-benar dikirim.

async function fetchWithForwardedCookie(event, url, options = {}) {
  const incoming = getRequestHeaders(event)
  const cookie = incoming.cookie || ''

  const headers = {
    ...(options.headers || {}),
    ...(cookie ? { cookie } : {})
  }

  console.log('SSR fetch url=', url)
  console.log('SSR fetch cookie=', headers.cookie)

  return await $fetch(url, {
    ...options,
    headers
  })
}

Jika log ini menunjukkan cookie kosong, root cause biasanya adalah cookie tidak diteruskan dari request user ke request SSR internal.

Log di backend API

Di backend, log informasi minimum:

  • Cookie yang diterima
  • Skema request yang dianggap backend
  • Host/origin
  • Hasil parsing session

Contoh yang berguna:

  • Apakah cookie name yang diharapkan memang ada?
  • Apakah backend menganggap request insecure sehingga cookie secure diabaikan di logika tertentu?
  • Apakah backend menghasilkan redirect atau set-cookie baru dengan domain yang salah?

Inspeksi header yang paling sering menjadi sumber masalah

1. Header Cookie

Ini pemeriksaan paling langsung. Pada SSR, cookie dari browser tidak boleh diasumsikan ikut otomatis ketika server Nuxt memanggil API. Anda sering perlu meneruskannya secara eksplisit.

Kesalahan umum:

  • Menggunakan fetch server-side tanpa header cookie
  • Mengandalkan interceptor client yang tidak berjalan pada SSR
  • Memisahkan domain app dan API tanpa memperhatikan scope cookie

2. Origin dan Host

Jika backend memvalidasi asal request, CSRF, atau domain cookie berdasarkan host tertentu, maka perbedaan antara app.example.com, api.example.com, dan host internal seperti api-internal bisa memicu perilaku berbeda.

Contoh masalah:

  • Browser menyimpan cookie untuk app.example.com, tetapi SSR memanggil API ke host internal yang tidak termasuk domain cookie.
  • Backend mengeluarkan cookie untuk domain yang tidak pernah dipakai browser.

3. X-Forwarded-Proto

Header ini penting ketika reverse proxy melakukan terminasi TLS. Dari sudut pandang browser, koneksi adalah HTTPS. Dari sudut pandang aplikasi di belakang proxy, request mungkin terlihat sebagai HTTP kecuali proxy meneruskan X-Forwarded-Proto: https dan aplikasi/backend mempercayai header tersebut.

Dampaknya:

  • Backend bisa salah membangun URL absolut.
  • Middleware auth bisa salah mendeteksi skema request.
  • Cookie secure atau redirect auth dapat berperilaku tidak konsisten.

4. SameSite

Atribut SameSite mengontrol kapan browser mau mengirim cookie lintas situs. Walau banyak kasus SSR gagal bukan semata karena SameSite, atribut ini tetap perlu diperiksa bila app dan API berada pada origin berbeda atau ada redirect login antar domain.

Sebagai pedoman umum:

  • SameSite=Lax cocok untuk banyak alur same-site biasa.
  • SameSite=None biasanya mensyaratkan Secure.
  • Jika konfigurasi terlalu ketat, browser bisa menyimpan cookie tetapi tidak mengirimkannya pada request tertentu.

Karena implementasi browser dan topologi deployment bisa berbeda, verifikasi langsung di DevTools tetap wajib.

5. Secure

Cookie dengan atribut Secure hanya dikirim melalui HTTPS dari sudut pandang browser. Ini benar dan disarankan untuk production. Namun masalah muncul bila salah satu komponen tidak menyadari bahwa request asli user datang lewat HTTPS.

Contoh:

  • Proxy menerima HTTPS lalu meneruskan ke app via HTTP.
  • App/backend tidak membaca X-Forwarded-Proto sehingga menganggap request bukan HTTPS.
  • Akibatnya URL callback, redirect, atau kebijakan cookie menjadi tidak tepat.

6. Domain

Atribut Domain harus cocok dengan strategi origin aplikasi Anda. Salah set domain adalah penyebab klasik.

  • Jika cookie dikeluarkan untuk api.example.com, browser tidak otomatis mengirimnya ke app.example.com.
  • Jika Nuxt SSR memanggil API memakai host internal, cookie berbasis browser tidak relevan pada host internal itu kecuali cookie diteruskan sebagai header oleh server.

Root cause yang paling umum pada kasus ini

Pada studi kasus ini, akar masalah yang paling konkret biasanya kombinasi berikut:

  1. Cookie session memang tersimpan di browser, sehingga login terlihat sukses.
  2. Request SSR dari Nuxt ke API tidak meneruskan header cookie, sehingga backend menganggap user anonim.
  3. Reverse proxy tidak meneruskan atau aplikasi tidak mempercayai header forwarded, terutama X-Forwarded-Proto, sehingga skema/origin yang digunakan pada auth menjadi salah.
  4. Konfigurasi cookie kurang cocok untuk topologi production, misalnya domain terlalu sempit, SameSite tidak sesuai, atau kombinasi SameSite=None tanpa Secure.

Inilah kenapa local terlihat normal: di lokal Anda sering tidak punya reverse proxy, host berbeda, TLS termination, atau domain production yang membuat aturan cookie menjadi signifikan.

Dampaknya pada alur auth SSR

Jika SSR gagal membawa cookie yang benar, dampaknya tidak hanya pada halaman profil.

  • Guard route berbasis user session gagal saat initial render.
  • Data personal tidak bisa di-prefetch di server.
  • HTML awal dirender sebagai guest, lalu berubah setelah hydration.
  • Muncul flicker UI atau mismatch state auth.
  • SEO dan cache behavior bisa kacau bila halaman sensitif auth salah dianggap publik.

Masalah ini juga berpotensi memicu bug turunan seperti redirect loop ke login, session invalidation palsu, atau cache halaman SSR yang tidak sesuai user.

Langkah perbaikan di layer Nuxt server

1. Forward cookie secara eksplisit pada SSR fetch

Untuk request server-side dari Nuxt ke backend, ambil cookie dari request masuk lalu teruskan ke API. Ini sering menjadi perbaikan inti.

// Utility SSR fetch yang meneruskan cookie dari request user
export async function apiFetch(event, path, options = {}) {
  const headers = getRequestHeaders(event)
  const cookie = headers.cookie

  return await $fetch(path, {
    baseURL: process.env.API_BASE_URL,
    ...options,
    headers: {
      ...(options.headers || {}),
      ...(cookie ? { cookie } : {})
    }
  })
}

Poin pentingnya bukan nama utilitas, tetapi prinsipnya: SSR fetch harus membawa header auth yang relevan.

2. Gunakan base URL yang konsisten dengan strategi auth

Hindari campuran asal request yang membingungkan. Pilih salah satu pendekatan:

  • Nuxt SSR memanggil API melalui origin publik yang sama dengan browser, jika arsitektur dan latency memungkinkan.
  • Nuxt SSR memanggil host internal, tetapi secara eksplisit meneruskan cookie/header yang diperlukan.

Jangan biarkan sebagian request memakai domain publik dan sebagian lagi host internal tanpa alasan yang jelas, karena ini menyulitkan debugging domain cookie dan CSRF.

3. Jika perlu, forward header tambahan yang relevan

Beberapa backend membutuhkan lebih dari sekadar cookie, misalnya header origin atau host tertentu untuk validasi. Lakukan dengan hati-hati dan hanya forward header yang benar-benar diperlukan.

const incoming = getRequestHeaders(event)

const forwardedHeaders = {
  cookie: incoming.cookie,
  'x-forwarded-proto': incoming['x-forwarded-proto'] || 'https',
  'x-forwarded-host': incoming['x-forwarded-host'] || incoming.host,
  origin: incoming.origin
}

Jangan meneruskan semua header mentah secara buta ke backend jika tidak perlu, karena dapat membawa risiko keamanan atau perilaku tak terduga.

Langkah perbaikan di reverse proxy

Proxy harus meneruskan informasi request asli dengan benar.

Contoh konfigurasi Nginx

location / {
    proxy_pass http://nuxt_upstream;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Inti yang perlu dipastikan:

  • Host asli user diteruskan.
  • X-Forwarded-Proto berisi https jika request publik memang HTTPS.
  • Jika ada beberapa layer proxy/load balancer, semua hop perlu dikonfigurasi konsisten.

Pastikan aplikasi/backend mempercayai proxy

Meneruskan header saja belum cukup bila aplikasi di belakang proxy tidak dikonfigurasi untuk mempercayainya. Pada banyak stack backend, ada konsep trusted proxy atau padanan sejenis. Tanpa ini, backend tetap bisa menganggap request sebagai HTTP internal biasa.

Catatan: Nama konfigurasi trusted proxy berbeda antar framework. Gunakan mekanisme resmi dari stack backend Anda untuk mempercayai reverse proxy, jangan mengandalkan header forwarded tanpa validasi sumber proxy.

Langkah perbaikan di konfigurasi cookie

1. Validasi domain cookie

Pastikan domain cookie sesuai dengan topologi aplikasi:

  • Jika app dan API dipisah subdomain, tentukan apakah cookie perlu berlaku lintas subdomain.
  • Jangan set domain yang terlalu sempit jika SSR atau browser perlu mengakses origin lain yang masih satu situs.
  • Jangan set domain ke host internal yang tidak pernah diakses browser.

2. Gunakan Secure di production HTTPS

Untuk production, cookie session sebaiknya bertanda Secure dan HttpOnly. Namun pastikan seluruh jalur publik memang HTTPS dan proxy meneruskan proto dengan benar.

3. Tinjau SameSite berdasarkan alur auth nyata

Jika login atau API melibatkan origin berbeda, periksa apakah SameSite terlalu restriktif. Jangan mengubah ke None tanpa alasan, tetapi jangan pula memaksa nilai terlalu ketat jika arsitekturnya memang membutuhkan pengiriman cookie lintas origin yang valid.

4. Hindari mismatch path

Walau jarang jadi akar utama pada kasus SSR reverse proxy, Path yang terlalu sempit juga bisa membuat cookie tampak ada tetapi tidak terkirim pada endpoint tertentu.

Contoh pola implementasi yang aman dan mudah di-debug

Salah satu pendekatan praktis adalah membuat satu wrapper fetch SSR untuk semua request auth-sensitive.

export async function serverApi(event, endpoint, options = {}) {
  const reqHeaders = getRequestHeaders(event)
  const cookie = reqHeaders.cookie

  return await $fetch(endpoint, {
    baseURL: process.env.API_BASE_URL,
    credentials: 'include',
    ...options,
    headers: {
      ...(options.headers || {}),
      ...(cookie ? { cookie } : {})
    }
  })
}

Hal yang perlu dipahami: pada konteks server-side, credentials: 'include' saja tidak selalu cukup menyelesaikan masalah, karena yang menentukan adalah apakah header cookie benar-benar ada pada request keluar. Karena itu forwarding eksplisit lebih mudah diverifikasi.

Checklist verifikasi pasca-fix

  1. Login menghasilkan Set-Cookie dengan atribut yang sesuai: domain, path, secure, httponly, samesite.
  2. Browser benar-benar menyimpan cookie tersebut.
  3. Request halaman ke server Nuxt membawa header Cookie.
  4. Log Nuxt menunjukkan cookie masuk pada request SSR.
  5. Wrapper fetch SSR meneruskan cookie ke backend API.
  6. Log backend menunjukkan cookie session diterima pada request dari SSR.
  7. Backend mengenali request sebagai HTTPS atau skema publik yang benar bila berada di belakang proxy.
  8. Reload halaman yang butuh auth tetap menampilkan user login pada initial SSR render.
  9. Tidak ada redirect loop login/logout.
  10. Perilaku local dan production sudah diuji dengan skenario reload, direct URL access, dan navigasi client-side.

Kesalahan umum yang sering menghabiskan waktu

  • Hanya memeriksa cookie di browser lalu menyimpulkan auth pasti berfungsi untuk SSR.
  • Tidak membedakan request browser dan request SSR di log.
  • Menggunakan host internal pada SSR tanpa memikirkan domain/origin/cookie.
  • Melupakan trusted proxy sehingga aplikasi salah mendeteksi HTTP vs HTTPS.
  • Mengubah SameSite secara acak tanpa memverifikasi kebutuhan arsitektur sebenarnya.
  • Tidak menguji hard reload, padahal bug auth SSR sering tidak terlihat saat navigasi client-side biasa.

Pelajaran debugging yang bisa diterapkan developer

1. Auth SSR adalah masalah aliran header, bukan sekadar status login

Pertanyaan kuncinya bukan “apakah user sudah login?”, melainkan “header apa yang masuk ke Nuxt, dan header apa yang keluar ke backend saat SSR?”.

2. Local sukses tidak membuktikan production aman

Masalah cookie sangat sensitif terhadap domain, HTTPS, reverse proxy, dan subdomain. Uji environment yang mendekati produksi sangat penting.

3. Selalu gambar rantai request end-to-end

Debugging menjadi lebih cepat jika Anda memetakan jalur berikut secara eksplisit:

  • Browser → Proxy
  • Proxy → Nuxt
  • Nuxt → API
  • API → Nuxt → Browser

Di setiap hop, periksa host, proto, origin, dan cookie.

4. Buat utilitas fetch SSR yang terstandar

Jangan sebarkan logika forwarding cookie ke banyak tempat. Satu wrapper fetch membuat perilaku auth lebih konsisten, mudah diuji, dan mudah diinspeksi saat terjadi insiden.

Penutup

Kasus Nuxt.js: debug cookie auth hilang saat SSR di reverse proxy hampir selalu berakar pada ketidaksinkronan antara browser, proxy, server Nuxt, dan backend API dalam memperlakukan cookie dan skema request. Login bisa tampak berhasil karena browser memang menyimpan session, tetapi SSR tetap gagal bila cookie tidak diteruskan atau backend salah membaca konteks HTTPS/origin.

Perbaikannya biasanya kombinasi tiga hal: forward cookie dengan benar di layer Nuxt server, teruskan header proxy seperti X-Forwarded-Proto secara konsisten, dan sesuaikan konfigurasi cookie agar cocok dengan domain serta alur produksi. Setelah itu, verifikasi dengan hard reload, log per hop, dan pembacaan header yang teliti. Pendekatan ini jauh lebih efektif daripada menebak-nebak di sisi frontend saja.