Audit log adalah catatan permanen dari semua aktivitas autentikasi dan otorisasi. Ini kritis untuk keamanan, debugging, dan kepatuhan regulasi (GDPR, SOC2, ISO 27001).
Mengapa audit log penting:
- Deteksi serangan (brute force, credential stuffing, account takeover)
- Investigasi insiden keamanan pasca-kejadian
- Bukti kepatuhan untuk auditor
- Debugging masalah akses user
Struktur audit log yang baik:
function createAuditLog(params) {
return {
id: crypto.randomUUID(),
timestamp: new Date().toISOString(),
userId: params.userId,
action: params.action, // "login", "logout", "password_change", "resource_access"
resource: params.resource, // "/api/users/42", "/api/admin/settings"
success: params.success, // true/false
metadata: {
ip: params.ip,
userAgent: params.userAgent,
...params.metadata // data tambahan spesifik action
}
};
}
// Contoh penggunaan:
createAuditLog({
userId: "user_42",
action: "login",
resource: "/api/auth/login",
success: true,
ip: "1.2.3.4",
userAgent: "Mozilla/5.0..."
});
createAuditLog({
userId: "user_42",
action: "resource_access",
resource: "/api/admin/users",
success: false,
ip: "1.2.3.4",
metadata: { reason: "insufficient_permissions", requiredRole: "admin" }
});
Tipe event yang harus di-log:
const AUTH_EVENTS = {
LOGIN_SUCCESS: "auth.login.success",
LOGIN_FAILED: "auth.login.failed",
LOGOUT: "auth.logout",
PASSWORD_CHANGED: "auth.password.changed",
PASSWORD_RESET_REQUESTED: "auth.password.reset_requested",
MFA_ENABLED: "auth.mfa.enabled",
MFA_VERIFIED: "auth.mfa.verified",
MFA_FAILED: "auth.mfa.failed",
TOKEN_REFRESHED: "auth.token.refreshed",
SESSION_REVOKED: "auth.session.revoked",
ROLE_CHANGED: "auth.role.changed",
ACCOUNT_LOCKED: "auth.account.locked",
};
Query & filter audit logs:
function queryAuditLogs(logs, filters = {}) {
return logs.filter((log) => {
if (filters.userId && log.userId !== filters.userId) return false;
if (filters.action && log.action !== filters.action) return false;
if (filters.success !== undefined && log.success !== filters.success) return false;
if (filters.from && new Date(log.timestamp) < new Date(filters.from)) return false;
if (filters.to && new Date(log.timestamp) > new Date(filters.to)) return false;
return true;
});
}
// Contoh: semua login gagal dalam 1 jam terakhir
const failedLogins = queryAuditLogs(logs, {
action: "auth.login.failed",
success: false,
from: new Date(Date.now() - 60 * 60 * 1000).toISOString()
});
// Deteksi brute force: >5 gagal dari IP yang sama
const byIp = failedLogins.reduce((acc, log) => {
const ip = log.metadata?.ip;
acc[ip] = (acc[ip] || 0) + 1;
return acc;
}, {});
const suspiciousIps = Object.entries(byIp).filter(([, count]) => count > 5);
Tips implementasi:
- Gunakan append-only storage (log tidak bisa diedit/dihapus)
- Simpan di storage terpisah dari database utama
- Retensi minimal 90 hari (GDPR), idealnya 1 tahun
- Kirim ke SIEM (Security Information and Event Management) untuk alerting real-time
- Enkripsi log yang mengandung data sensitif