Skip to content

Mengapa JWT Pengguna Terhapus Harus Mengembalikan 401, Bukan 500

Adityo Guni Waluyo

Token JWT yang sah secara tanda tangan tetap bisa menunjuk pengguna yang sudah dihapus. Pengalaman memetakan sql.ErrNoRows ke 401 agar frontend membersihkan sesi.

Ringkasan

Jadi token user yang udah dihapus masih nembak API terus, dan middleware balikin 500, bikin frontend nggak mau hapus sesi dan user dapet banner error doang. Perbaikannya gampang: cek errors.Is(err, sql.ErrNoRows), kalau usernya nggak ada balikin 401 biar frontend bisa purge sesi. Ini juga sesuai RFC 9110 dan 6750, error lain tetap 500.

Saya sedang memantau log permintaan pada sesi pengujian QA di KotaPortal. Sesi tersebut secara resmi telah ditutup, namun saya melihat token lama terus menghantam endpoint API di latar belakang. Alih-alih diarahkan ke halaman login, layar monitor justru dipenuhi oleh banner error 500 Internal Server Error yang berulang. Sistem tidak mengalami crash, tetapi perilaku ini menjebak pengguna dalam loop notifikasi hingga token tersebut benar-benar kedaluwarsa secara alami.

Asumsi yang Menyesatkan

Banyak pengembang beranggapan bahwa selama tanda tangan kriptografis pada token JWT masih valid, maka sesi tersebut otomatis dianggap sah. Padahal, tanda tangan digital hanya membuktikan identitas pembuat token pada saat token tersebut diterbitkan. Mekanisme tersebut tidak memiliki kemampuan untuk memverifikasi apakah entitas pengguna yang bersangkutan masih terdaftar aktif di dalam database pada saat permintaan diproses.

Perbaikan pada Middleware

Sebelum perbaikan ini, fungsi ResolveBidang pada middleware autentikasi memperlakukan semua error database sebagai kegagalan umum. Ketika proses pencarian pengguna tidak menemukan baris data, database mengembalikan sentinel error yang spesifik. Dalam bahasa pemrograman Go, sentinel ini didefinisikan sebagai sql.ErrNoRows 3. Saya memperbarui logika middleware untuk memeriksa kondisi ini secara eksplisit menggunakan fungsi errors.Is yang diperkenalkan pada Go 1.13 4.

Di sisi repository, pencarian bidang berbasis user_id memakai QueryRow dan Scan. Kalau barisnya sudah tidak ada, Scan mengembalikan sentinel bawaan pustaka standar:

var ErrNoRows = errors.New("sql: no rows in result set")

Sentinel inilah yang menjadi garis pemisah klasifikasi: 3

if errors.Is(err, sql.ErrNoRows) {
    // Validly-signed token naming a deleted principal is an
    // authentication failure: 401 lets the frontend purge the
    // stale session (shouldClearSession) instead of a 500 banner.
    slog.Warn("token user no longer exists", "user_id", userID)
    response.Error(w, http.StatusUnauthorized, "UNAUTHORIZED",
        "sesi tidak valid — pengguna tidak ditemukan, silakan login ulang")
    return
}

Setiap error pencarian lain yang bukan merupakan ErrNoRows tetap dikembalikan sebagai 500 Internal Server Error. Ini memastikan bahwa kegagalan infrastruktur yang sesungguhnya tidak disamarkan sebagai masalah autentikasi. Ketergantungan ini sangat kritis. Di sisi frontend, kebijakan pembersihan sesi atau shouldClearSession hanya dipicu secara eksklusif oleh respons 401. Jika API secara keliru mengembalikan 500, frontend akan mengasumsikan bahwa terjadi gangguan jaringan sementara. Akibatnya, frontend tidak akan menghapus token yang tersimpan, dan permintaan berikutnya akan terus gagal dengan pola yang identik.

Perubahan sekecil ini tetap saya kawal dengan dua lapis regresi: test integrasi yang dulu membekukan perilaku lama (karakterisasi 500) saya balik agar menuntut 401 UNAUTHORIZED beserta pesan errornya, dan test unit middleware menambah kasus error lain tetapjatuh ke 500. Sifat fail-closed middleware tidak berubah — yang berubah hanya status untuk satu kasus yang memang milik autentikasi.

Kepatuhan terhadap Semantik HTTP

Klasifikasi kode status ini bukan sekadar preferensi gaya penulisan kode, melainkan bentuk kepatuhan wajib terhadap semantik protokol HTTP. RFC 9110 bagian 15.5.2 menyatakan secara eksplisit bahwa kode status 401 (Unauthorized) menunjukkan bahwa permintaan tidak diterapkan karena tidak memiliki kredensial autentikasi yang valid untuk sumber daya target 1. Ketika principal atau pengguna yang disebutkan dalam kredensial tidak lagi ada di sistem, kredensial tersebut secara definisi operasional tidak lagi valid.

Dokumen RFC 6750 bagian 3.1 mengenai kategori invalid_token juga memperkuat argumen ini. Spesifikasi tersebut menegaskan bahwa resource SHOULD merespons dengan kode status HTTP 401 ketika token akses kedaluwarsa, dicabut, atau tidak valid karena alasan lainnya 2. Penghapusan akun pengguna secara administratif adalah contoh sempurna dari kasus "tidak valid karena alasan lainnya" yang dimaksud oleh spesifikasi tersebut.

Mengklasifikasikan error dengan tepat adalah sebuah fitur fungsional, bukan sekadar detail implementasi.

Kode status HTTP berfungsi sebagai kontrak komunikasi yang ketat antara layanan backend dan antarmuka frontend. Mengembalikan kode 500 untuk masalah yang sejatinya adalah masalah autentikasi bukan hanya melanggar standar RFC, tetapi juga secara aktif merusak mekanisme pemulihan mandiri yang telah dirancang oleh antarmuka pengguna.

Sumber

[1] RFC 9110, section 15.5.2 (401 Unauthorized), diakses 13 Oktober 2026.
[2] RFC 6750, section 3.1 (invalid_token), diakses 13 Oktober 2026.
[3] Go database/sql, ErrNoRows, diakses 13 Oktober 2026.
[4] Working with Errors in Go 1.13, diakses 13 Oktober 2026.

Artikel terkait