Authentication Logging and Audit — Tracking Auth Events for Security and Compliance
In this tutorial, you will learn about Authentication Logging and Audit. We cover key concepts, practical examples, and best practices to help you master this topic.
Authentication logging records every authentication event — successes, failures, token issuances, and revocations — in a structured format for security monitoring, compliance auditing, and Incident Response.
What You'll Learn
Structured auth event logging, audit trail requirements for SOC2 and GDPR, log aggregation with ELK/Datadog, suspicious pattern detection, and alerting on auth anomalies.
Why It Matters
Without auth logging, you cannot detect brute force attacks, credential stuffing, or token theft. Audit logs are required for SOC2, HIPAA, and PCI-DSS compliance. Structured logs enable automated threat detection.
Real-World Use
AWS CloudTrail logs every API authentication event. Auth0 provides detailed auth logs. Durga Antivirus Pro logs all authentication events with structured JSON, feeding into a SIEM for real-time threat detection.
Code Example: Structured Auth Logging Middleware
import json, time, uuid
from flask import Flask, request, g, has_request_context
import logging
app = Flask(__name__)
# Configure structured JSON logging
class JSONFormatter(logging.Formatter):
def format(self, record):
log_entry = {
"timestamp": self.formatTime(record),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage()
}
if hasattr(record, "auth_event"):
log_entry["auth"] = record.auth_event
return json.dumps(log_entry)
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
app.logger.addHandler(handler)
app.logger.setLevel(logging.INFO)
def log_auth_event(event_type, **details):
"""Log a structured authentication event."""
event = {
"event_id": str(uuid.uuid4()),
"event_type": event_type,
"timestamp": time.time(),
"ip": request.remote_addr if has_request_context() else "unknown",
"user_agent": request.headers.get("User-Agent", "") if has_request_context() else "",
**details
}
app.logger.info(f"Auth event: {event_type}", extra={"auth_event": event})
@app.before_request
def log_auth_attempt():
"""Log authentication attempts before processing."""
if request.path.startswith("/api/auth/"):
auth_header = request.headers.get("Authorization", "")
has_token = bool(auth_header and auth_header.startswith("Bearer "))
if request.path == "/api/auth/login":
username = request.json.get("username", "") if request.is_json else ""
log_auth_event(
"login_attempt",
username=username,
method="password",
endpoint=request.path
)
@app.after_request
def log_auth_result(response):
"""Log authentication results after processing."""
if request.path.startswith("/api/auth/"):
if response.status_code == 401:
log_auth_event(
"login_failure",
status_code=401,
endpoint=request.path
)
elif response.status_code == 200 and "login" in request.path:
log_auth_event(
"login_success",
status_code=200,
endpoint=request.path
)
return response
Code Example: Audit Trail for Token Operations
import sqlite3
class AuthAuditTrail:
"""Stores immutable audit records of auth events."""
def __init__(self, db_path="auth_audit.db"):
self.conn = sqlite3.connect(db_path)
self._create_table()
def _create_table(self):
self.conn.execute("""
CREATE TABLE IF NOT EXISTS auth_audit (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL,
event_type TEXT NOT NULL,
user_id TEXT,
ip_address TEXT,
user_agent TEXT,
details TEXT,
hash TEXT NOT NULL
)
""")
self.conn.commit()
def _compute_hash(self, row_data):
"""Compute SHA-256 hash of the previous row + current data for tamper evidence."""
prev_hash = self.conn.execute(
"SELECT hash FROM auth_audit ORDER BY id DESC LIMIT 1"
).fetchone()
prev_hash = prev_hash[0] if prev_hash else "0" * 64
return hashlib.sha256(
(prev_hash + json.dumps(row_data, sort_keys=True)).encode()
).hexdigest()
def record(self, event_type, user_id=None, ip_address=None,
user_agent=None, details=None):
"""Record an immutable audit entry."""
import datetime
row = {
"timestamp": datetime.datetime.utcnow().isoformat(),
"event_type": event_type,
"user_id": user_id,
"ip_address": ip_address,
"user_agent": user_agent,
"details": json.dumps(details) if details else None
}
row["hash"] = self._compute_hash(row)
self.conn.execute("""
INSERT INTO auth_audit
(timestamp, event_type, user_id, ip_address, user_agent, details, hash)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", tuple(row.values()))
self.conn.commit()
return row
def verify_integrity(self):
"""Verify the audit trail has not been tampered with."""
rows = self.conn.execute(
"SELECT id, timestamp, event_type, user_id, ip_address, "
"user_agent, details, hash FROM auth_audit ORDER BY id"
).fetchall()
prev_hash = "0" * 64
for row in rows:
row_dict = {
"timestamp": row[1],
"event_type": row[2],
"user_id": row[3],
"ip_address": row[4],
"user_agent": row[5],
"details": row[6]
}
expected_hash = hashlib.sha256(
(prev_hash + json.dumps(row_dict, sort_keys=True)).encode()
).hexdigest()
if expected_hash != row[7]:
return False, f"Tampering detected at row {row[0]}"
prev_hash = row[7]
return True, "Audit trail intact"
audit = AuthAuditTrail()
# Usage during login
def login_with_audit():
user = authenticate_user(request.json)
if user:
token = issue_token(user)
audit.record(
event_type="token_issued",
user_id=user["id"],
ip_address=request.remote_addr,
user_agent=request.headers.get("User-Agent"),
details={"token_type": "access", "expires_in": 900}
)
return jsonify({"access_token": token})
else:
audit.record(
event_type="login_failed",
user_id=request.json.get("username"),
ip_address=request.remote_addr,
details={"reason": "invalid_password"}
)
return jsonify({"error": "Invalid credentials"}), 401
Code Example: Suspicious Activity Detection
import time
from collections import defaultdict, deque
class SuspiciousActivityDetector:
"""Detect suspicious auth patterns and trigger alerts."""
def __init__(self):
self.failed_attempts = defaultdict(lambda: deque(maxlen=100))
self.alert_handlers = []
def on_alert(self, handler):
self.alert_handlers.append(handler)
def _trigger_alert(self, alert_type, details):
for handler in self.alert_handlers:
handler(alert_type, details)
def record_attempt(self, username, ip_address, success):
"""Record an auth attempt and check for suspicious patterns."""
now = time.time()
if not success:
self.failed_attempts[ip_address].append(now)
self.failed_attempts[username].append(now)
# Check: brute force from single IP
ip_failures = [
t for t in self.failed_attempts[ip_address]
if t > now - 300
]
if len(ip_failures) >= 10:
self._trigger_alert("brute_force_ip", {
"ip": ip_address,
"attempts": len(ip_failures),
"window": "5 minutes"
})
# Check: credential stuffing (many usernames from one IP)
username_count = len(set(
self.failed_attempts.keys()
))
if username_count > 20 and not success:
pass
# Check: impossible travel (same user from different geo)
# Requires geo-IP lookup integration
if success:
self._check_impossible_travel(username, ip_address)
def _check_impossible_travel(self, username, ip_address):
"""Detect if the same user logs in from distant locations rapidly."""
pass # Implement with geo-IP database
detector = SuspiciousActivityDetector()
@detector.on_alert
def send_security_alert(alert_type, details):
"""Send alert to security team."""
print(f"[ALERT] {alert_type}: {json.dumps(details)}")
# In production: send to Slack, PagerDuty, or SIEM
@app.after_request
def detect_suspicious(response):
if request.path == "/api/auth/login":
username = request.json.get("username", "")
success = response.status_code == 200
detector.record_attempt(username, request.remote_addr, success)
return response
Common Mistakes
1. Logging Passwords or Tokens
Never log plaintext passwords, full tokens, or secrets. Log token IDs (jti), truncated tokens, or hashes. Token truncation: first 8 chars + "...".
2. No Tamper Protection
Standard logs can be modified by an attacker. Use hash chaining (Blockchain-style) for audit trails. Each entry includes the hash of the previous entry.
3. Logging Without Context
Log entries missing IP, user agent, timestamp, or event ID are useless for investigation. Always include at least: timestamp, event type, user identifier, source IP.
4. Ignoring Log Storage Costs
Auth logs grow quickly. Implement log rotation and retention policies. Keep detailed logs for 90 days, summary logs for 1 year (or as required by compliance).
5. No Log Monitoring
Collecting logs without monitoring is security theater. Set up alerts for: >5 failed logins in 1 minute, logins from new locations, logins outside business hours.
Practice Questions
- What should be included in every auth log entry?
- How does hash chaining prevent audit trail tampering?
- What patterns indicate a brute force attack in auth logs?
- What is the difference between logging and auditing?
- Why should passwords never appear in auth logs?
Answers:
- Timestamp, event type, user identifier, source IP, user agent, success/failure status, event ID. Include request path and response status code.
- Each entry includes the hash of the previous entry. Changing any entry breaks all subsequent hashes. Tampering is detected by recomputing the chain.
- Multiple failed logins (10+) from a single IP in 5 minutes, or failed logins for many different usernames from one IP in a short window.
- Logging captures events for debugging and monitoring. Auditing creates an immutable, tamper-evident record for compliance and legal purposes.
- If the log is compromised, passwords in plaintext are exposed. Hash or mask sensitive fields. Log the user identifier instead of the password.
Challenge: Build a complete auth logging and audit system with structured JSON logging, tamper-evident audit trail with hash chaining, suspicious activity detection with alerts, and a log viewer dashboard.
FAQ
Mini Project
Build a Flask API with structured JSON auth logging, tamper-evident SQLite audit trail with hash chaining, suspicious activity detection (brute force, credential stuffing), and a simple dashboard for reviewing auth events.
What's Next
Now learn about Authentication Security and OWASP Top 10 for securing your authentication implementation against common vulnerabilities.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro