Menu docs

Fitur & Build Notes · · 2 menit baca

Session auth stateless di Workers: HMAC cookie + PBKDF2

Autentikasi panel admin tanpa database session dan tanpa library: token HMAC-SHA256 di cookie, verifikasi password PBKDF2 via Web Crypto, dan perbandingan constant-time — source code lengkap.

Web CryptoCloudflare WorkersSecurityTypeScript

Panel admin situs ini diamankan tanpa database session, tanpa Redis, dan tanpa library auth — hanya Web Crypto API bawaan Cloudflare Workers. Dokumentasi ini membedah dua bagiannya: session cookie stateless ber-HMAC dan verifikasi password PBKDF2.

Kenapa stateless?

Workers berjalan di ratusan lokasi sekaligus. Session yang disimpan di memori atau database berarti round-trip tambahan di tiap request. Session stateless menaruh semua info di cookie itu sendiri — server tinggal memverifikasi tanda tangannya.

Format token: <unix-expiry>.<base64url(HMAC-SHA256)>

1. Menandatangani session

async function signSession(env: Env, payload: string): Promise<string> {
  const key = await crypto.subtle.importKey(
    "raw", sessionSecret(env), { name: "HMAC", hash: "SHA-256" }, false, ["sign"],
  );
  const sig = await crypto.subtle.sign("HMAC", key, enc("admin-session:" + payload));
  return b64urlEncode(sig);
}

export async function newSessionToken(env: Env): Promise<string> {
  const exp = String(Math.floor(Date.now() / 1000) + SESSION_TTL_S); // 7 hari
  return exp + "." + (await signSession(env, exp));
}

Verifikasi = tanda tangani ulang expiry yang diklaim, bandingkan constant-time, lalu cek belum kedaluwarsa:

export async function validSession(env: Env, token: string): Promise<boolean> {
  const [expStr, sig] = splitOnce(token, ".");
  const want = await signSession(env, expStr);
  if (!constantTimeEqual(enc(sig), enc(want))) return false; // anti timing attack
  const exp = Number.parseInt(expStr, 10);
  return Number.isFinite(exp) && Date.now() / 1000 < exp;
}

Cookie-nya HttpOnly, Secure, SameSite=Strict — JavaScript di browser tidak pernah bisa membacanya.

2. Password: PBKDF2, bukan bcrypt

Workers tidak punya bcrypt native, tapi Web Crypto menyediakan PBKDF2. Hash disimpan dalam format pbkdf2:<iterasi>:<salt>:<hash> dan diverifikasi begini:

const key = await crypto.subtle.importKey("raw", enc(pass), "PBKDF2", false, ["deriveBits"]);
const bits = await crypto.subtle.deriveBits(
  { name: "PBKDF2", hash: "SHA-256", salt, iterations }, // 100.000 — batas maksimum Workers
  key,
  want.length * 8,
);
return constantTimeEqual(new Uint8Array(bits), want);

Catatan penting: runtime Workers membatasi PBKDF2 di 100.000 iterasi — di bawah rekomendasi OWASP (600k untuk SHA-256). Kompensasinya: password admin yang panjang dan acak (passphrase 20+ karakter), sehingga brute-force tetap tidak ekonomis.

3. Middleware penjaga

export const requireAuth: MiddlewareHandler = async (c, next) => {
  if (!(await isAuthed(c))) return c.redirect("/admin/login", 303);
  await next();
};

adminRoutes.use("/admin/*", requireAuth); // login/logout dikecualikan

Pelajaran

  • crypto.subtle di Workers cukup untuk auth solo-admin — tanpa dependensi eksternal sama sekali.
  • Selalu bandingkan hash dengan XOR constant-time, jangan === — perbedaan waktu bisa membocorkan informasi.
  • Session stateless punya trade-off: tidak bisa di-revoke satuan sebelum kedaluwarsa. Untuk satu admin, ganti SESSION_SECRET = logout paksa semua sesi.

Mau fitur seperti ini dibangun untuk bisnismu?