Belajar ExpressJS - Authentication & Authorization
Episode 12 of 28

Belajar ExpressJS - Authentication & Authorization

Membangun autentikasi dan otorisasi: hashing password dengan bcrypt, JWT dan session cookie, middleware protect untuk route terproteksi, serta RBAC berbasis peran untuk authorization.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Setelah di episode 9-10 kalian punya data dan di episode 11 halaman, episode 12 menjawab pertanyaan keamanan pertama: siapa kalian (authentication) dan apa yang boleh kalian lakukan (authorization). Dua konsep ini sering dicampur, padahal berbeda: autentikasi membuktikan identitas, otorisasi menentukan hak.

Mengapa ini bagian terpenting? Karena autentikasi yang salah adalah celah yang memicu sebagian besar kebocoran data. Di episode ini kita membangun hashing password yang benar, dua mekanisme sesi (JWT dan cookie), middleware protect, dan RBAC — pola yang sama dipakai aplikasi produksi nyata.

Hashing Password dengan bcrypt

Mengapa Tidak Bisa Plaintext

Menyimpan password mentah berarti satu kebocoran database = semua akun terbongkar. Yang disimpan adalah hash — hasil fungsi satu arah yang tidak bisa dibalik. bcrypt adalah pilihan standar karena punya salt otomatis dan cost factor yang bisa dinaikkan seiring waktu:

Install bcrypt
npm install bcrypt
JSHashing dan verifikasi password
import bcrypt from "bcrypt"
 
const SALT_ROUNDS = 12
 
export async function hashPassword(plain) {
  return bcrypt.hash(plain, SALT_ROUNDS)
}
 
export async function verifyPassword(plain, hashed) {
  return bcrypt.compare(plain, hashed)
}

SALT_ROUNDS = 12 menentukan beban komputasi hash — di tahun 2026, 12 adalah titik seimbang antara keamanan dan kecepatan. Jangan memakai round di bawah 10 untuk kredensial pengguna.

Important

Password tidak pernah boleh masuk ke schema User yang di-return ke client. Saat mengirim user, selalu seleksi field — contoh: { id, name, email, role, createdAt } — tanpa password. Kebocoran hash pun tetap berbahaya, apalagi hash yang berisi password.

Dua Mekanisme Sesi

Opsi A: JWT (Stateless Token)

JWT adalah token yang ditandatangani server; client menyimpannya dan mengirimnya di header Authorization. Stateless — server tidak perlu menyimpan sesi:

Install jsonwebtoken
npm install jsonwebtoken
JSSigning dan verifikasi JWT
import jwt from "jsonwebtoken"
 
const JWT_SECRET = process.env.JWT_SECRET
 
export function signToken(payload) {
  return jwt.sign(payload, JWT_SECRET, { expiresIn: "15m" })
}
 
export function verifyToken(token) {
  return jwt.verify(token, JWT_SECRET)
}

expiresIn: "15m" membuat akses token berumur pendek — pola standar: akses token singkat, refresh token panjang (tidak dibahas penuh di series ini, tapi konsepnya sama).

Kebalikan dari JWT: data sesi disimpan di server, client hanya membawa identifier acak di cookie HttpOnly:

Install express-session
npm install express-session
JSSetup express-session
import session from "express-session"
 
app.use(
  session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: {
      httpOnly: true,
      sameSite: "lax",
      secure: process.env.NODE_ENV === "production",
      maxAge: 24 * 60 * 60 * 1000,
    },
  })
)

httpOnly: true mencegah JavaScript client membaca cookie (perlindungan XSS), secure: true hanya mengirim lewat HTTPS. Session store default menyimpan di memori — untuk produksi ganti ke store Redis (episode 21-22) agar sesi bertahan lintas instance.

Login Flow

Endpoint login memverifikasi kredensial dan menerbitkan token:

JSPOST /auth/login - login JWT
import { verifyPassword } from "../utils/password.js"
import { signToken } from "../utils/jwt.js"
 
export const login = async (req, res) => {
  const { email, password } = req.body || {}
 
  const user = await User.findOne({ email })
  if (!user) {
    throw new AppError(401, "INVALID_CREDENTIALS", "Email atau password salah")
  }
 
  const ok = await verifyPassword(password, user.password)
  if (!ok) {
    throw new AppError(401, "INVALID_CREDENTIALS", "Email atau password salah")
  }
 
  const token = signToken({ sub: user.id, role: user.role })
  res.json({ token, user: { id: user.id, email: user.email, role: user.role } })
}

Pesan error yang sama untuk "user tidak ada" dan "password salah" mencegah penyerang memetakan email yang terdaftar — kebocoran informasi kecil yang sering dilupakan.

Middleware protect

Middleware protect memvalidasi token dan menempelkan user ke req — pola yang sama sekali lagi kita lihat di episode 5:

JSsrc/middleware/auth.js
import { verifyToken } from "../utils/jwt.js"
 
export const protect = async (req, res, next) => {
  const header = req.get("Authorization") || ""
 
  if (!header.startsWith("Bearer ")) {
    throw new AppError(401, "UNAUTHORIZED", "Token tidak ada")
  }
 
  const token = header.slice(7)
 
  try {
    const payload = verifyToken(token)
    req.user = { id: payload.sub, role: payload.role }
    next()
  } catch {
    throw new AppError(401, "INVALID_TOKEN", "Token tidak valid atau kedaluwarsa")
  }
}

Route dilindungi tinggal memasang middleware di posisi kedua:

JSMelindungi route dengan protect
router.get("/me", protect, (req, res) => {
  res.json({ user: req.user })
})

req.user yang diisi middleware bisa dibaca handler di bawahnya — pola memperkaya req yang kita kenalkan sejak episode 5 kini membuahkan hasil.

Authorization: RBAC

Peran dan Fungsi Akses

RBAC (Role-Based Access Control) membatasi aksi berdasarkan peran. Middleware requireRole dibangun sebagai factory (pola episode 5):

JSsrc/middleware/rbac.js
export const requireRole = (...roles) => {
  return (req, res, next) => {
    if (!req.user) {
      throw new AppError(401, "UNAUTHORIZED", "Silakan login dulu")
    }
    if (!roles.includes(req.user.role)) {
      throw new AppError(403, "FORBIDDEN", "Akses ditolak untuk peran ini")
    }
    next()
  }
}

Pemakaian berantai dengan protect:

JSRoute dengan RBAC
router.delete("/users/:id", protect, requireRole("admin"), deleteUser)
router.get("/users/:id", protect, (req, res) => res.json({ ok: true }))
  • Semua route yang butuh identitas → protect.
  • Aksi admin-only → tambah requireRole("admin").
  • Status 403 untuk peran yang tidak diizinkan; 401 untuk yang belum login. Pembeda ini penting dipahami klien.

Mendefinisikan Peran

JSDefinisi peran
const ROLES = {
  admin: ["read:all", "write:all", "delete:all"],
  editor: ["read:all", "write:own"],
  viewer: ["read:own"],
}
 
export const can = (role, permission) => ROLES[role]?.includes(permission)

Pendekatan permission-based ini lebih fleksibel daripada hardcode nama peran di tiap route — kita pakai pola ini sebagai bahan diskusi di episode 13 dan 23.

Common Pitfalls

Menyimpan Password di Schema yang Di-return

Selalu eksklusi password dari response. Salah satu cara: method toSafeJSON() di model, atau seleksi field eksplisit di tiap query.

JWT Secret Kuat (dan Rahasia)

JWT_SECRET harus random dan panjang (minimal 32 byte). Jangan pernah hardcode di source atau commit — dikelola lewat env (episode 16).

Sesi di Memory Default

express-session default memakai MemoryStore — hilang saat server restart dan tidak bisa dipakai lintas instance. Untuk produksi, gunakan Redis store (episode 22).

Warning

Mengirim JWT di body response untuk disimpan localStorage rentan XSS. Alternatif yang lebih aman: cookie HttpOnly dengan secure. Pilihannya bergantung arsitektur, tetapi token di localStorage + aplikasi dengan XSS = akun bisa dibajak. Kita perkuat lapisan ini di episode 18.

Penutup

Episode 12 membangun fondasi keamanan identitas: hashing bcrypt dengan salt, dua mekanisme sesi (JWT stateless vs session cookie), middleware protect yang mengisi req.user, dan RBAC berbasis peran dengan status 401/403 yang tepat.

Inti yang harus dibawa pulang:

  • Hash password dengan bcrypt, salt rounds ≥ 10; jangan pernah menyimpan plaintext.
  • JWT untuk token stateless; session cookie HttpOnly untuk sesi server-side.
  • Middleware protect memvalidasi token dan mengisi req.user.
  • requireRole(...roles) membatasi aksi; 403 = peran salah, 401 = belum login.
  • Jangan kembalikan password ke client dalam bentuk apa pun.

Di episode 13 selanjutnya kita akan memvalidasi input: validation & sanitization — zod dan express-validator, sanitasi input, dan pemetaan error validasi ke response 400 yang konsisten. Sampai jumpa di episode 13!

Belajar ExpressJS - Authentication & Authorization | Belajar ExpressJS