Audit log autentikasi adalah catatan permanen setiap kejadian penting di sistem login: siapa, melakukan apa, kapan, dari mana, dan berhasil atau gagal. Catatan ini cuma boleh ditambah, gak pernah diubah atau dihapus. Bayangin mutasi rekening bank. Tiap transaksi jadi satu baris baru. Kalau ada transfer yang salah, bank gak menghapus barisnya, tapi menambah baris koreksi. Justru karena gak ada yang bisa dihapus, mutasi itu bisa dipercaya sebagai bukti.
🧪 Tebakan cepat: log pagi ini di Portal Sekolah punya 10 baris, 3 di antaranya gagal. Penyaringnya ditulis
if (filters.success && log.success !== filters.success) return false, lalu dipanggil dengan{ success: false }. Berapa baris yang tampil? Tebak dulu, jawabannya ada di bagian "Query: semua syarat harus cocok".
Event apa yang dicatat
Audit log auth mencatat setiap kejadian yang mengubah siapa bisa masuk, atau membuktikan siapa yang masuk:
action |
Kapan ditulis | Contoh di Portal Sekolah |
|---|---|---|
login |
setiap percobaan login, berhasil maupun gagal | Siti salah password, lalu berhasil |
logout |
user keluar | Pak Andi keluar dari laptop sekolah |
mfa |
kode 2FA dicek, berhasil maupun gagal | kode TOTP Pak Andi salah sekali |
password_change |
password diganti atau di-reset | Siti mengganti password |
role_change |
role atau permission seseorang berubah | Bu Dewi mengubah role Rizky jadi alumni |
token_revoke |
sesi atau token dicabut | dua sesi lain Siti dicabut |
Page view biasa bukan event auth; tempatnya di access log web server. Mengawasi log untuk alert, misalnya banyak login gagal dari satu IP, dibahas di lesson Security Logging & Monitoring di track Security. Lesson ini membangun catatannya.
Lima pertanyaan di tiap baris
Setiap baris menjawab lima pertanyaan yang sama:
- Siapa:
userIdpelakunya. Di contoh ini"user1"adalah Siti,"user2"Pak Andi,"user3"Bu Dewi. Untukrole_change, orang yang diubah ditulis dimetadata.target, karena pelakunya Bu Dewi. - Apa:
action(jenis kejadian) danresource(endpoint atau data yang disentuh). - Kapan:
timestampdalam UTC, format ISO 8601, darinew Date().toISOString().2026-10-09T00:00:20.000Zadalah Jumat pukul 07.00.20 WIB. UTC dipakai supaya log dari server di zona waktu mana pun bisa diurutkan bersama. - Dari mana: alamat IP dan user agent (browser dan sistem operasinya), di
metadata. - Hasilnya:
success, nilaitrueataufalse(boolean, bukan teks), plus alasan gagalnya dimetadata.reason.
function createAuditLog(userId, action, resource, success, metadata) {
return {
userId: userId,
action: action,
resource: resource,
success: success,
metadata: metadata,
timestamp: new Date().toISOString(),
};
}
const entry = createAuditLog("user1", "login", "/auth/login", false, {
ip: "36.68.10.21",
userAgent: "Chrome 141 di Windows 11",
reason: "wrong_password",
});
console.log(entry.userId, entry.success, typeof entry.timestamp); // user1 false string
- Semua argumen disimpan apa adanya.
successtetap boolean, jadi bisa disaring dengan===. timestampdibuat di server saat event terjadi, bukan dikirim client. HasiltoISOString()adalah string.
Yang gak boleh masuk log
Log dibaca banyak orang dan banyak alat. Apa pun yang tertulis di sana ikut terbuka ke mereka semua, dan di log append-only gak bisa dihapus lagi. Jadi jangan pernah mencatat:
- Password, termasuk yang salah. Password salah sering cuma beda satu huruf dari yang asli.
- Token, session ID, kode OTP, recovery code: siapa pun yang membacanya bisa memakainya.
Kalau perlu menunjuk sesi yang dicabut, catat id barisnya ("sess_1_1"), bukan isi token-nya. Pilih field satu per satu; jangan pernah menyimpan seluruh isi request, karena body login membawa password.
Append-only: cuma boleh ditambah
Audit log berguna sebagai bukti hanya kalau gak bisa diedit. Kalau admin, atau orang yang berhasil memakai akun admin, bisa menghapus baris, jejak kejadiannya bisa ikut hilang. Buat itu mustahil di level database, bukan cuma di kode aplikasi. Di PostgreSQL, user database milik aplikasi cuma diberi izin menambah dan membaca:
GRANT SELECT, INSERT ON audit_logs TO portal_app;
Kalau kode aplikasi mencoba mengubah baris, database menolak:
portal=> UPDATE audit_logs SET success = true WHERE id = 1;
ERROR: permission denied for table audit_logs
Di Laravel, tambahkan pagar di model juga:
class AuditLog extends Model
{
public const UPDATED_AT = null; // baris gak pernah di-update
protected static function booted(): void
{
static::updating(fn () => false); // batalkan setiap update
static::deleting(fn () => false); // batalkan setiap delete
}
}
Lalu tulis barisnya dari event auth bawaan Laravel, misalnya Illuminate\Auth\Events\Failed untuk login gagal dan Login untuk login berhasil. Untuk lapis tambahan, kirim salinan log ke layanan terpisah yang server aplikasimu gak bisa hapus.
Query: semua syarat harus cocok
Mencari jejak berarti menyaring log dengan beberapa syarat sekaligus. Syarat yang gak dikirim gak boleh mempersempit hasil:
function queryAuditLogs(logs, filters) {
return logs.filter((log) => {
if (filters.userId !== undefined && log.userId !== filters.userId) return false;
if (filters.action !== undefined && log.action !== filters.action) return false;
if (filters.success !== undefined && log.success !== filters.success) return false;
return true; // lolos semua syarat yang dikirim
});
}
Dengan 10 baris log Portal Sekolah pagi ini, persis yang ada di Lab:
| Query | Hasil |
|---|---|
{ success: false } |
3: login Siti 07.00.05, kode 2FA Pak Andi 07.05.15, login Siti 07.31.00 |
{ userId: "user1" } |
6: semua kejadian Siti |
{ userId: "user1", success: false } |
2: dua login Siti yang gagal |
Penyaring di tebakan tadi menulis filters.success && .... Saat filters.success bernilai false, ekspresi itu langsung false, syaratnya dilewati, dan semua 10 baris tampil. Itu jawaban tebakannya. Bandingkan dengan !== undefined: nilai false tetap dianggap syarat. Di latihan, dengan empat log contoh, tiga query yang sama menghasilkan 2, 3 dan 1.
⚠️ Jebakan yang sering ketemu
- Cuma mencatat yang gagal. Login berhasil dari IP yang aneh justru tanda akun sudah dipakai orang lain. Catat dua-duanya.
- Menyimpan password, token, atau kode OTP. Pilih field satu per satu, dan catat id sesi, bukan token-nya.
- Tabel log bisa di-UPDATE dan DELETE. Cabut izinnya di database, jangan cuma mengandalkan kode.
- Waktu lokal tanpa zona.
09/10/2026 07:00gak jelas zonanya. Simpan ISO 8601 UTC. if (filters.success && ...). Nilaifalsedianggap "gak ada syarat". Periksa dengan!== undefined.
TL;DR: Audit log auth mencatat login (berhasil dan gagal), logout, MFA, ganti password, perubahan role, dan pencabutan token. Tiap baris menjawab siapa (userId), apa (action, resource), kapan (timestamp ISO 8601 UTC dari toISOString()), dari mana (IP, user agent), dan hasilnya (success boolean, plus alasannya). Jangan pernah mencatat password, token, session ID atau kode OTP. Simpan append-only: database cuma mengizinkan INSERT dan SELECT, supaya log bisa dipercaya sebagai bukti. Query menyaring dengan semua syarat yang dikirim sekaligus, dan syarat yang gak dikirim (undefined) gak mempersempit hasil.