InfrastructureInfrastructure Plugins

Audit Logs

Record security and admin activity around Kernia auth events.

Better Auth Infrastructure includes audit-log endpoints as part of its hosted dashboard. Kernia keeps audit logs application-owned: you record events through hooks, plugin callbacks, and admin route wrappers, then expose them to your dashboard.

Audit logs are different from request logs. They should answer who changed what, when it changed, which authorization context allowed it, and whether any secrets or sensitive values were redacted.

Event model

FieldPurpose
idStable event id.
actor_idUser or service account that performed the action.
eventMachine-readable event name.
subject_typeUser, session, organization, key, product, entitlement, or config.
subject_idSubject identifier.
request_idCorrelates the audit event with request logs.
ip_addressSource IP if available.
user_agentUser agent if available.
metadataRedacted structured context.
created_atEvent timestamp.

Events to record

AreaEvents
Usersuser.created, user.updated, user.deleted, user.banned, user.unbanned
Sessionssession.created, session.revoked, sessions.revoked_all
Accountsaccount.linked, account.unlinked, password.changed
Admin configauth_method.enabled, auth_method.disabled, email_client.updated, provider.updated
Organizationsorganization.created, member.added, member.removed, role.updated
API keysapi_key.created, api_key.revoked, api_key.rotated
Billingstripe.synced, subscription.updated, entitlement.granted, usage.tracked
Securitycaptcha.failed, rate_limit.blocked, session.risk_flagged

Server usage

audit.py
from datetime import datetime, timezone
from uuid import uuid4

async def audit_event(adapter, *, actor_id, event, subject_type, subject_id, metadata=None):
    await adapter.create("audit_event", {
        "id": str(uuid4()),
        "actor_id": actor_id,
        "event": event,
        "subject_type": subject_type,
        "subject_id": subject_id,
        "metadata": redact(metadata or {}),
        "created_at": datetime.now(timezone.utc),
    })

Wrap admin writes instead of relying on the frontend:

admin_config.py
@router.patch("/auth-methods/{method}")
async def update_method(method: str, body: AuthMethodUpdate, session=Depends(require_admin)):
    updated = await config_service.set_method_enabled(method, body.enabled)
    await audit_event(
        adapter,
        actor_id=session.user_id,
        event="auth_method.enabled" if body.enabled else "auth_method.disabled",
        subject_type="auth_method",
        subject_id=method,
        metadata={"enabled": body.enabled},
    )
    return updated

Redaction

Audit metadata must never store OAuth client secrets, SMTP passwords, Stripe restricted keys, SCIM bearer tokens, API key plaintext values, password reset tokens, OTP codes, or session tokens. Store a fingerprint or last four characters when operators need to distinguish credentials.

Admin query

Expose audit logs only to admins. Search by actor, subject, event type, and time range.

GET /api/admin/audit-events?event=auth_method.disabled&limit=50

Retention

Choose retention based on compliance needs. Authentication security events are usually kept longer than request logs. If events include personal data, document deletion behavior and export requirements.