Autentikasi di aplikasi web modern tidak lagi sekadar soal form login dan menyimpan token di browser. Pada aplikasi berbasis Nuxt, terutama ketika memanfaatkan server routes dan runtime Nitro, desain autentikasi harus mempertimbangkan keamanan cookie, model rendering SSR, komunikasi client-server, dan bagaimana kredensial dibawa antar request. Di sinilah pembahasan tentang autentikasi Nuxt, JWT Nuxt, dan security Nuxt terbaru menjadi relevan.
Artikel ini membahas pola autentikasi yang umum dipakai di Nuxt saat ini, yaitu session berbasis cookie dan JWT. Kita akan membandingkan kapan masing-masing pendekatan lebih cocok, bagaimana menyimpan token dengan aman, cara melindungi route, menerapkan middleware, menangani refresh token, serta mengurangi risiko CSRF dan XSS. Di bagian akhir, ada contoh implementasi login sederhana menggunakan server routes Nuxt.
Mengapa Server Routes dan Nitro Penting untuk Autentikasi
Nuxt modern menyediakan server routes melalui Nitro, yang memungkinkan kita menulis endpoint backend langsung di dalam proyek Nuxt. Ini berguna untuk autentikasi karena beberapa operasi sensitif sebaiknya tidak dikerjakan sepenuhnya di browser, misalnya:
- Memverifikasi kredensial pengguna terhadap database atau service lain.
- Membuat session server-side.
- Menandatangani dan memverifikasi JWT.
- Menyetel cookie dengan atribut keamanan seperti
HttpOnly,Secure, danSameSite. - Menyembunyikan rahasia seperti JWT secret atau kredensial OAuth dari client.
Dengan arsitektur ini, browser tidak perlu mengetahui seluruh detail mekanisme autentikasi. Client cukup memanggil endpoint seperti /api/login, /api/me, dan /api/logout, sementara validasi utama dilakukan di server route.
Session Berbasis Cookie vs JWT di Nuxt
1. Session berbasis cookie
Pada pendekatan session, server menyimpan status login pengguna, biasanya dalam penyimpanan seperti memory store, Redis, atau database. Browser hanya membawa session identifier melalui cookie. Saat request masuk, server membaca session ID, mengambil data session, lalu menentukan apakah pengguna sudah login.
Kelebihan:
- Cookie
HttpOnlylebih aman dari akses JavaScript, sehingga mengurangi risiko pencurian token akibat XSS. - Mudah melakukan revokasi session karena status ada di server.
- Cocok untuk aplikasi web tradisional, SSR, dashboard internal, dan aplikasi dengan domain yang terkontrol.
Kekurangan:
- Membutuhkan penyimpanan session di server atau cache terpusat jika aplikasi berjalan di banyak instance.
- Perlu perhatian khusus terhadap CSRF jika autentikasi bergantung pada cookie yang otomatis terkirim.
2. JWT
Pada pendekatan JWT, server menerbitkan token yang berisi klaim pengguna, lalu client mengirim token tersebut pada request berikutnya, biasanya melalui header Authorization: Bearer <token>. Token dapat diverifikasi tanpa menyimpan state login di server jika menggunakan pendekatan stateless.
Kelebihan:
- Cocok untuk arsitektur API, mobile app, atau komunikasi antar-service.
- Lebih mudah diskalakan secara stateless untuk access token.
- Berguna jika frontend dan backend berada pada domain atau aplikasi yang berbeda.
Kekurangan:
- Revokasi token tidak sesederhana session murni, terutama jika token belum kedaluwarsa.
- Jika token disimpan sembarangan, misalnya di
localStorage, risikonya meningkat terhadap XSS. - JWT yang terlalu besar atau berisi data sensitif adalah kesalahan umum.
Kapan memilih yang mana?
Untuk banyak aplikasi Nuxt berbasis web, session cookie atau cookie berisi token HttpOnly sering menjadi pilihan paling aman dan praktis. Jika Anda membuat SPA/SSR yang berbicara ke backend sendiri, mengirimkan cookie aman sering lebih sederhana daripada mengekspos token ke JavaScript.
JWT lebih masuk akal jika:
- Anda punya API yang dipakai oleh web, mobile, atau pihak ketiga.
- Anda butuh arsitektur stateless untuk access token.
- Anda dapat mendesain alur rotasi dan revokasi token dengan benar.
Catatan: JWT bukan otomatis lebih aman daripada session. Keamanan lebih ditentukan oleh cara penyimpanan, rotasi, validasi, dan pembatasan token.
Penyimpanan Token yang Aman di Aplikasi Nuxt
Kesalahan paling umum pada implementasi JWT Nuxt adalah menyimpan token akses di localStorage atau sessionStorage tanpa mitigasi XSS yang kuat. Penyimpanan ini memang mudah, tetapi dapat diakses oleh JavaScript di halaman. Jika ada XSS, token bisa dicuri.
Pendekatan yang lebih aman untuk aplikasi web adalah:
- Simpan access token berumur pendek di cookie
HttpOnlyjika memungkinkan. - Simpan refresh token di cookie
HttpOnly,Secure, danSameSiteyang ketat. - Gunakan masa hidup access token yang singkat.
- Lakukan rotasi refresh token dan simpan hash refresh token di server jika perlu revokasi.
Contoh penyetelan cookie di server route:
setCookie(event, 'access_token', accessToken, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
maxAge: 60 * 15
})
setCookie(event, 'refresh_token', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/api/auth/refresh',
maxAge: 60 * 60 * 24 * 7
})Mengapa ini bekerja?
HttpOnlymencegah JavaScript browser membaca cookie secara langsung.Securememastikan cookie hanya dikirim melalui HTTPS.SameSitemembantu mengurangi risiko CSRF dengan membatasi kapan cookie ikut terkirim lintas situs.pathdapat dipersempit agar refresh token hanya ikut ke endpoint tertentu.
Contoh Implementasi Login Sederhana dengan Server Routes
Berikut contoh sederhana alur login di Nuxt menggunakan Nitro server route. Contoh ini fokus pada pola, bukan integrasi database lengkap.
Server route login
// server/api/login.post.ts
import { readBody, setCookie, createError } from 'h3'
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const { email, password } = body
if (!email || !password) {
throw createError({ statusCode: 400, statusMessage: 'Email dan password wajib diisi' })
}
// Ganti dengan validasi user ke database
const user = email === 'admin@example.com' && password === 'rahasia'
? { id: 1, email, role: 'admin' }
: null
if (!user) {
throw createError({ statusCode: 401, statusMessage: 'Kredensial tidak valid' })
}
const accessToken = await signAccessToken({ sub: user.id, role: user.role })
const refreshToken = await signRefreshToken({ sub: user.id })
setCookie(event, 'access_token', accessToken, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
maxAge: 60 * 15
})
setCookie(event, 'refresh_token', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/api/refresh',
maxAge: 60 * 60 * 24 * 7
})
return {
user: {
id: user.id,
email: user.email,
role: user.role
}
}
})Utility verifikasi user dari cookie
// server/utils/auth.ts
import { getCookie, createError } from 'h3'
export async function requireUser(event: any) {
const token = getCookie(event, 'access_token')
if (!token) {
throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
}
try {
const payload = await verifyAccessToken(token)
return payload
} catch {
throw createError({ statusCode: 401, statusMessage: 'Token tidak valid atau kedaluwarsa' })
}
}Endpoint profil saat pengguna sudah login
// server/api/me.get.ts
import { requireUser } from '~/server/utils/auth'
export default defineEventHandler(async (event) => {
const user = await requireUser(event)
return { user }
})Form login di client
<script setup lang="ts">
const email = ref('')
const password = ref('')
const error = ref('')
const login = async () => {
error.value = ''
try {
await $fetch('/api/login', {
method: 'POST',
body: { email: email.value, password: password.value }
})
await navigateTo('/dashboard')
} catch (e: any) {
error.value = e?.data?.statusMessage || 'Login gagal'
}
}
</script>Pada alur ini, browser tidak perlu membaca token. Cookie dikirim otomatis ke server route yang relevan, dan server memutuskan apakah request sah atau tidak.
Proteksi Route dan Middleware di Nuxt
Proteksi route di Nuxt biasanya dilakukan pada dua lapisan:
- Client route middleware untuk pengalaman pengguna, misalnya mengarahkan pengguna yang belum login ke halaman login.
- Server-side authorization untuk keamanan yang sesungguhnya, karena semua pembatasan tetap harus diverifikasi di server.
Contoh route middleware
// middleware/auth.ts
export default defineNuxtRouteMiddleware(async () => {
try {
await $fetch('/api/me')
} catch {
return navigateTo('/login')
}
})Lalu pakai di halaman:
definePageMeta({
middleware: ['auth']
})Penting untuk dipahami bahwa middleware client tidak cukup untuk keamanan. Pengguna tetap bisa memanggil endpoint API secara langsung. Karena itu, endpoint sensitif harus selalu memverifikasi identitas dan peran pengguna.
Otorisasi berbasis role
Setelah autentikasi berhasil, Anda bisa menerapkan otorisasi berbasis role atau permission. Misalnya, endpoint admin hanya menerima pengguna dengan role admin. Jangan bergantung pada data role dari state client; ambil dari token yang sudah diverifikasi atau dari database.
Refresh Token dan Rotasi Token
Jika access token dibuat berumur pendek, pengguna tidak perlu login ulang terus-menerus selama refresh token masih valid. Pola yang umum adalah:
- Pengguna login dan menerima access token + refresh token.
- Access token digunakan untuk request rutin.
- Saat access token kedaluwarsa, client memanggil endpoint refresh.
- Server memverifikasi refresh token, menerbitkan access token baru, dan idealnya refresh token baru juga.
Contoh endpoint refresh:
// server/api/refresh.post.ts
import { getCookie, setCookie, createError } from 'h3'
export default defineEventHandler(async (event) => {
const refreshToken = getCookie(event, 'refresh_token')
if (!refreshToken) {
throw createError({ statusCode: 401, statusMessage: 'Refresh token tidak ada' })
}
const payload = await verifyRefreshToken(refreshToken)
const newAccessToken = await signAccessToken({ sub: payload.sub, role: payload.role })
setCookie(event, 'access_token', newAccessToken, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
maxAge: 60 * 15
})
return { ok: true }
})Untuk security Nuxt terbaru, praktik yang lebih baik adalah refresh token rotation: setiap refresh menghasilkan refresh token baru, dan token lama dinonaktifkan. Dengan begitu, jika refresh token lama bocor, server dapat mendeteksi penggunaan ulang.
CSRF, XSS, dan Praktik Keamanan yang Direkomendasikan
1. Perlindungan CSRF
Jika autentikasi memakai cookie, browser akan mengirim cookie secara otomatis. Ini membuka peluang CSRF pada request yang mengubah state, seperti POST, PUT, PATCH, atau DELETE. Mitigasinya:
- Gunakan
SameSite=LaxatauStrictbila memungkinkan. - Terapkan CSRF token untuk endpoint sensitif.
- Validasi header seperti
OriginatauRefereruntuk request dari browser. - Jangan gunakan GET untuk operasi yang mengubah data.
2. Perlindungan XSS
XSS sering menjadi akar pencurian token dan pengambilalihan sesi. Langkah minimumnya:
- Hindari menyimpan token di
localStoragejika tidak perlu. - Jangan merender HTML mentah tanpa sanitasi.
- Gunakan Content Security Policy bila memungkinkan.
- Escape output yang berasal dari input pengguna.
3. Validasi di server, bukan hanya di client
Semua pemeriksaan akses harus dilakukan di server route. Client-side checks hanya untuk UX. Ini prinsip dasar yang sering terlewat pada implementasi autentikasi Nuxt.
4. Batasi isi token
Jangan masukkan data sensitif seperti password hash, API key, atau profil lengkap pengguna ke dalam JWT. Isi token sebaiknya minimal, misalnya sub, role, iat, dan exp.
5. Logout yang benar
Logout bukan hanya menghapus state di client. Untuk cookie-based auth, hapus cookie di server dengan maxAge: 0 atau tanggal kedaluwarsa lampau. Jika memakai refresh token yang disimpan server-side, tandai token tersebut sebagai revoked.
Kesalahan Umum dan Tips Debugging
- Cookie tidak tersimpan: biasanya karena atribut
securepada environment HTTP lokal, domain/path salah, atau request cross-site tidak memenuhi kebijakan cookie. - 401 padahal sudah login: cek apakah cookie terkirim ke endpoint yang benar, token kedaluwarsa, atau secret verifikasi tidak sama.
- Middleware redirect loop: pastikan halaman login tidak ikut diproteksi oleh middleware yang sama.
- SSR dan client state tidak sinkron: jangan hanya mengandalkan store client; gunakan endpoint seperti
/api/meuntuk memuat identitas pengguna secara konsisten. - Refresh token gagal diam-diam: periksa path cookie refresh token dan pastikan endpoint refresh sesuai.
Untuk debugging, periksa tab Network di browser, lihat header Set-Cookie, cookie yang terkirim pada request berikutnya, dan log server Nitro untuk memverifikasi kapan token diterbitkan dan ditolak.
Rekomendasi Arsitektur Praktis
Untuk mayoritas aplikasi web Nuxt modern, pola yang seimbang adalah:
- Gunakan server routes Nitro untuk login, logout, refresh, dan endpoint profil.
- Simpan access token berumur pendek di cookie
HttpOnly. - Simpan refresh token di cookie
HttpOnlyterpisah dengan path sempit. - Verifikasi autentikasi di server untuk setiap endpoint sensitif.
- Gunakan middleware Nuxt untuk UX, bukan sebagai satu-satunya kontrol akses.
- Terapkan CSRF protection untuk request yang memodifikasi data.
Jika kebutuhan Anda lebih kompleks, misalnya integrasi mobile app, multi-client API, atau SSO, JWT tetap sangat relevan. Namun untuk aplikasi Nuxt yang dominan berjalan di browser, kombinasi cookie aman dan server routes sering memberi ergonomi terbaik sekaligus meningkatkan keamanan.
Pada akhirnya, keputusan antara session dan JWT bukan soal mana yang lebih modern, melainkan mana yang paling tepat untuk model aplikasi Anda. Dengan memanfaatkan autentikasi Nuxt lewat Nitro secara benar, memahami trade-off JWT Nuxt, dan menerapkan security Nuxt terbaru seperti cookie aman, rotasi refresh token, validasi server-side, serta mitigasi CSRF/XSS, Anda bisa membangun sistem autentikasi yang tidak hanya berfungsi, tetapi juga tahan terhadap kesalahan umum di dunia nyata.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!