Audit Log
nauthera-server records security-relevant activity to an audit_events table in its own
database. Every record names its organization, actor, target, outcome and request and
trace IDs. The set of actions is closed: the catalog is part of the API contract and
can be read at GET /v1/admin/audit-catalog.
Example actions:
| Area | Actions |
|---|---|
| Authentication | auth.sign_in.completed, auth.sign_out.completed, auth.registration.completed, auth.account.locked, auth.rate_limit.triggered |
| MFA | mfa.totp.enrollment_started, mfa.totp.activated, mfa.totp.verified, mfa.recovery_code.consumed, mfa.recovery_codes.regenerated, mfa.factor.locked, mfa.totp.disabled |
| Users | user.created, user.updated, user.deleted, user.password.changed, user.read, user.listed |
| Clients | client.registered, client.configured, client.deregistered, client.provisioned, client.secret.issued, client.name.suspicious |
| Sessions | session.created, session.revoked |
| Admin | admin.access.denied |
| System | system.started, system.migration.applied, system.admin.bootstrapped, system.audit.overflow, system.audit.write_failed |
Levels
audit.level sets how much is recorded. Each level includes everything in the levels
before it:
| Level | Records |
|---|---|
off | Only the events that cannot be suppressed |
security | Authentication outcomes, credential and MFA changes, privilege grants, every administrative change |
write (default) | Every other state change |
read | Reads too: list and get endpoints, UserInfo, introspection |
trace | High-volume protocol traffic, mainly token issuance and refresh |
Fine-tune with globs over action names (* also matches across dots):
audit:
level: write
include: ["oauth.token.issued"] # record these regardless of level
exclude: ["mfa.status.read"] # never record these; beats level and includeAn exclude pattern that matches no action in the catalog is a startup error, so a
typo cannot silently do nothing.
Events that cannot be suppressed
Some events are recorded whatever level and exclude say: deleting a user, changing a
password, disabling MFA, granting a role, revealing a client secret, and dropping audit
history. An operator who could switch off the record of their own destructive actions
would have a privilege-escalation path. At startup the server logs exactly which events
the resolved policy suppresses.
Delivery and durability
- Critical events are written synchronously. Everything else is queued and written
in batches (
buffer_size,batch_size,flush_interval). - When the buffer is full, events are dropped rather than blocking requests. Every
drop is counted in
nauthera_audit_events_dropped_totaland recorded as asystem.audit.overflowevent that carries the count. Alert onnauthera_audit_buffer_depthbefore drops start. audit.fail_closed: truemakes a request fail if a non-suppressible event cannot be persisted, so a privileged action never completes unaudited. It is off by default, because for an identity provider it turns an audit-table problem into an outage for everyone.- On shutdown, the buffer is drained after the listeners stop and before the database closes.
Retention
audit.retention defaults to 9600h (400 days). That covers PCI DSS 10.5.1's twelve
months with a month of slack. On PostgreSQL the table is partitioned by month,
partition_ahead_months (6) partitions are created ahead of time, and whole partitions
are dropped once they expire. On SQLite, expired rows are deleted by range.
Querying
curl -s "https://auth.example.com/v1/admin/orgs/default/audit-events?action=user.*,mfa.*&outcome=failure&limit=100" \
-H "Authorization: Bearer $TOKEN"This needs the nauthera.audit:read scope and the manage-org role.
Getting the scope. The reserved
{slug}-admin-*clients are provisioned with the organization and user scopes only, notnauthera.audit:read, and discovery does not list it inscopes_supported. To read the audit log, register a client with that scope through the gRPC AdminService (dynamic registration refusesnauthera.*scopes), and sign in with it as a user who holdsmanage-org.
Filters:
| Parameter | Meaning |
|---|---|
from and to | RFC3339 window. Defaults to the last 30 days. |
action | Comma-separated action patterns (* and ? wildcards). |
category, outcome, severity | Comma-separated values. |
actor_id, target_id, target_type | Who did it, and to what. |
client_ip, client_id, session_id, request_id, trace_id | Correlation. |
cross_tenant_only | Only actions taken on this organization by an actor from another one. |
Results use the same cursor pagination as the rest of the
admin API and can be sorted by id
or occurred_at. GET /v1/admin/orgs/{slug}/audit-events/{id} returns a single record.
The Docker Compose stack ships a Grafana audit-log explorer dashboard that reads
audit_events directly (/d/nauthera-audit).
Known gaps
Changes to clients and branding made through the gRPC control plane are not recorded yet (#301). Raising audit coverage across the control plane is tracked in #286 and #303.