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.
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.subtledi 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?