Sampai sekarang siapa pun bisa memanggil semua endpoint Toko Buku. Padahal GET /profile harus tahu kamu siapa, dan DELETE /books/3 cuma boleh untuk admin. Autentikasi menjawab satu pertanyaan: siapa yang mengirim request ini? Bayangin gelang konser. Di loket kamu tunjukkan KTP sekali (login), lalu dapat gelang. Sesudah itu, tiap masuk area cukup tunjukkan gelangnya, penjaga gak menanyakan KTP lagi. Gelang itu token. Gelang palsu atau yang sudah lewat tanggalnya ditolak di pintu. Gelang asli kelas festival yang mau masuk area VIP juga ditolak, tapi dengan alasan lain.
🧪 Tebakan cepat: Siti login dengan benar dan punya token yang sah, tapi role-nya
customer. Dia mengirimDELETE /books/3, yang cuma untuk admin. Server menjawab401atau403? Tebak dulu, jawabannya ada di bagian "401 atau 403".
Token di header Authorization
- Client login sekali:
POST /auth/logindengan email dan password. Server memeriksanya (cara menyimpan dan mencocokkan password ada di pelajaran Password Hashing di track Auth), lalu menjawab dengan token. - Di tiap request berikutnya, client mengirim token itu di header
Authorization:
GET /profile HTTP/1.1
Host: api.tokobuku.test
Authorization: Bearer valid-token-123
Authorizationadalah nama header standar untuk kredensial.Beareradalah skemanya, artinya "pembawa": siapa pun yang membawa token ini dianggap pemiliknya, persis gelang konser. SesudahBearerada satu spasi, lalu tokennya.- Token dikirim di header, bukan di query string
?token=.... URL tercatat di log server dan riwayat browser, jadi token di sana gampang bocor.
Middleware requireAuth
Pemeriksaannya ditaruh di middleware, function yang jalan sebelum handler dan boleh meneruskan atau menghentikan request:
function requireAuth(req, res, next) {
const header = req.headers.authorization || "";
const [scheme, token] = header.split(" ");
if (scheme !== "Bearer" || token !== "valid-token-123") {
res.set("WWW-Authenticate", 'Bearer realm="tokobuku"');
return res.status(401).json({ error: "Unauthorized" });
}
next();
}
app.get("/profile", requireAuth, (req, res) => {
res.json({ name: "Budi", role: "admin" });
});
- Express menulis nama header dengan huruf kecil, jadi
Authorizationdibaca darireq.headers.authorization. Kalau gak dikirim, isinyaundefined, makanya ada|| "". header.split(" ")memecah"Bearer valid-token-123"jadi["Bearer", "valid-token-123"].- Token yang gak ada dan token yang salah sama-sama dijawab
401dengan{"error": "Unauthorized"}. next()meneruskan request ke handler. Tanpanext()dan tanpa jawaban, request-nya menggantung sampai timeout.requireAuthditaruh di antara path dan handler, jadi cuma route itu yang dijaga.- Token tetap
"valid-token-123"cuma untuk latihan. Token sungguhan dibahas di bagian "Token yang ditandatangani".
Header Authorization |
Jawaban |
|---|---|
| (gak dikirim) | 401 |
Bearer salah |
401 |
valid-token-123 (tanpa Bearer) |
401 |
Bearer valid-token-123 |
200 |
401 atau 403
401 Unauthorized: server gak tahu kamu siapa. Token gak ada, salah, atau kedaluwarsa. Standar HTTP mewajibkan 401 membawa headerWWW-Authenticateyang menyebut cara login, misalnyaBearer realm="tokobuku". Untuk token yang rusak, tambahkanerror="invalid_token". Namanya memang menyesatkan: artinya "belum terautentikasi".403 Forbidden: server tahu kamu siapa, tapi kamu gak boleh melakukan itu. Login ulang gak membantu.
Siti sudah dikenali dari tokennya, jadi bukan 401. Dia cuma gak berhak menghapus buku: 403. Itu jawaban tebakannya.
| Siapa | GET /profile |
DELETE /books/3 |
|---|---|---|
| Tanpa token | 401 |
401 |
Siti, customer |
200 |
403 |
Budi, admin |
200 |
204 |
Memeriksa role (siapa boleh apa) disebut authorization. Bedanya dengan autentikasi dibahas di pelajaran Authentication vs Authorization di track Auth.
Token yang ditandatangani
valid-token-123 gampang ditebak. Token sungguhan ada dua jenis: string acak panjang yang dicocokkan ke database, atau token yang ditandatangani. Yang kedua, misalnya JWT (JSON Web Token), terdiri dari tiga bagian dipisah titik: header.payload.signature.
- Payload berisi data user, misalnya
{"sub":"2","name":"Siti","role":"customer","exp":1893456000}.expadalah waktu kedaluwarsa dalam detik sejak 1970, di sini 1 Januari 2030. Payload cuma di-encode base64url, jadi siapa saja bisa membacanya. Jangan taruh password di sana. - Signature adalah HMAC-SHA256 dari
header.payloaddengan kunci rahasia (secret) yang cuma dipegang server. Tanpa secret, gak ada yang bisa membuat signature yang cocok.
const crypto = require("crypto");
function verify(token, secret) {
const [header, payload, signature] = token.split(".");
const expected = crypto.createHmac("sha256", secret).update(header + "." + payload).digest("base64url");
if (signature !== expected) return null; // isinya diubah, atau bukan buatan server ini
const data = JSON.parse(Buffer.from(payload, "base64url").toString());
if (data.exp * 1000 < Date.now()) return null; // sudah kedaluwarsa
return data;
}
Siti mengganti "role":"customer" jadi "role":"admin" di payload-nya. Server menghitung ulang signature-nya, hasilnya zNM3i5yo..., padahal token membawa RlA4atrp.... Gak cocok: 401. Di aplikasi sungguhan, pakai library (misalnya jsonwebtoken atau jose), simpan secret di environment variable, dan bandingkan signature dengan crypto.timingSafeEqual. Detailnya ada di pelajaran JSON Web Token (JWT) di track Auth.
API key atau token user
| Token user | API key | |
|---|---|---|
| Mewakili | satu orang yang login | satu aplikasi atau server |
| Didapat dari | POST /auth/login |
dibuat di dashboard |
| Umur | pendek (menit sampai hari) | panjang, sampai dicabut |
| Dikirim lewat | Authorization: Bearer ... |
Authorization: Bearer ... atau X-API-Key: ... |
| Contoh | aplikasi HP pembeli | gudang yang sinkron stok tiap malam |
Server menyimpan API key dalam bentuk hash, seperti password, supaya database yang bocor gak ikut membocorkan key-nya.
⚠️ Jebakan yang sering ketemu
- Token di query string.
GET /profile?token=...tercatat di log dan riwayat browser. Kirim lewat headerAuthorization. - Lupa
Beareratau spasinya.Authorization: valid-token-123gak punya skema, jadi ditolak401. - 401 dan 403 tertukar. Belum dikenali:
401plusWWW-Authenticate. Dikenali tapi gak berhak:403. - Lupa
next(). Token benar, tapi request menggantung karena gak diteruskan ke handler. - Token di
localStorage.localStorage.setItem("token", jwtToken)bisa dibaca script apa pun di halaman, jadi rentan XSS: satu script jahat cukup untuk mencuri tokennya. CookiehttpOnlygak bisa dibaca JavaScript. Pembahasannya ada di pelajaran Secure Storage di Browser di track Security. - Menganggap payload JWT rahasia. Base64url itu encoding, bukan enkripsi.
TL;DR: Autentikasi menjawab "siapa yang mengirim request ini?". Client login sekali, lalu mengirim token di header Authorization dengan skema Bearer: Authorization: Bearer <token>. Middleware requireAuth membaca req.headers.authorization, memanggil next() kalau tokennya sah, dan menjawab 401 dengan {"error": "Unauthorized"} plus WWW-Authenticate kalau token gak ada, salah, atau kedaluwarsa. Dikenali tapi gak berhak, misalnya customer yang menghapus buku: 403. Token yang ditandatangani (JWT) punya payload yang bisa dibaca siapa saja dan signature HMAC-SHA256 yang cuma bisa dibuat pemegang secret, jadi payload yang diubah ketahuan. API key untuk aplikasi, token untuk user. Jangan simpan token di localStorage (rentan XSS) dan jangan kirim lewat query string.