go-auth
Guides

Audit Logs

Turning on audit logging, what gets recorded, and the two admin endpoints for querying it.

Audit Logs

Audit logging is off by default. When it's on, every significant thing that happens — logins, password changes, admin actions, org membership changes — gets published as a structured audit.Event to an internal queue, processed by background workers, and (if you configured a store) queryable through the two endpoints below.

Configuration

goauth.WithAudit(goauth.AuditConfig{
    Enabled:       true,                    // optional, default false
    FailureMode:   audit.AuditFailureOpen,   // optional, default fail-open — or audit.AuditFailureClosed
    RetentionDays: 90,                       // optional, default 90 (0 = forever)
    QueueSize:     1000,                     // optional, default 1000
    Workers:       3,                        // optional, default 3
    BatchSize:     50,                       // optional, default 50
    FlushInterval: 100 * time.Millisecond,   // optional, default 100ms
    Sinks:         []audit.EventSink{myKafkaSink}, // optional, default none
})
goauth.WithAuditSink(myOtherSink) // optional — adds one more sink at a time
FieldTypeDefaultNotes
EnabledboolfalseThe two endpoints below return 404 audit_not_configured while this is off.
FailureModeaudit.AuditFailureModefail-openFail-open: if publishing an event errors, the request that triggered it still succeeds. Fail-closed: that request fails instead — use this only if you have a compliance requirement that an action must not happen without a corresponding log entry.
RetentionDaysint900 means keep forever.
QueueSizeint1000Events are published async onto this queue; if it's full, behavior follows FailureMode.
Workersint3Background goroutines draining the queue.
BatchSizeint50Events per batch write to the store.
FlushIntervaltime.Duration100msMax time a partial batch waits before flushing anyway.
Sinks[]audit.EventSinknoneExtra destinations — Kafka, NATS, a webhook — fired in addition to the queryable store.

WithAuditSink appends one sink at a time without needing to repeat the whole config; calling WithAudit more than once doesn't duplicate sinks already added.

This is separate from application logging

config.Logger (the *slog.Logger you pass to WithLogger) is for operational logs — errors, warnings, debug output. Audit events are a distinct, structured stream meant to answer "who did what, when" for security review or compliance, and only exist at all if Enabled: true here.

Frontend client setup

The Client examples below call the same apiRequest(baseUrl, method, path, body) helper used throughout these guides — see Client → Setup. The named-method wrapper for both endpoints on this page lives on Client → Admin.

What gets recorded

Every event carries a type, a severity (info / warning / error / critical), whether it successed, and — depending on the event — an actor, a target user, a session, an org, an IP, and a user agent. The full type list, grouped by area:

AreaEvent types
Loginlogin.success, login.failed, login.locked, logout, admin.login.success, admin.login.failed
Registrationuser.registered
Emailemail.verification.sent, email.verified
Passwordpassword.changed, password.reset.requested, password.reset.completed
Sessionssession.created, session.refreshed, session.revoked, session.revoked_all
OAuthoauth.login, oauth.linked, oauth.unlinked
Adminadmin.user.created, admin.user.updated, admin.user.deleted, admin.user.banned, admin.user.unbanned
Rolesrole.changed
Organizationsorganization.created, organization.deleted, organization.member.invited, organization.member.removed

This list can grow across versions — treat event_type as an opaque string in your filters rather than an exhaustive enum you validate against.

Querying audit logs

List all audit logs — GET /admin/audit-logs

Query params (all optional)

ParamTypeNotes
offsetintDefault 0
limitintDefault 50, max 200
event_typestringExact match against type
actor_idstringWho performed the action
target_user_idstringWho the action was performed on
session_idstring
org_idstring
searchstringMatches metadata and user agent
from, toRFC3339 datetimeSilently ignored if the value doesn't parse — no error, just no filter applied
successtrue | falseAnything else is ignored

Response (200 OK)

{
  "events": [
    {
      "id": "...",
      "type": "login.failed",
      "severity": "warning",
      "success": false,
      "actorId": null,
      "targetUserId": null,
      "sessionId": null,
      "orgId": null,
      "ip": "192.168.1.1",
      "userAgent": "Mozilla/5.0 ...",
      "requestId": "...",
      "metadata": {},
      "createdAt": "2026-08-09T12:00:00Z"
    }
  ],
  "total": 1000,
  "limit": 50,
  "offset": 0
}

Errors

CodeStatusCause
audit_not_configured404WithAudit(AuditConfig{Enabled: true}) was never set
internal_error500Query failure against the store

curl

curl "https://api.myapp.com/admin/audit-logs?event_type=login.failed&limit=50" \
  -H "Origin: https://myapp.com" \
  -b admin-cookies.txt

Programmatic (Go)

events, total, err := auth.Services.AuditLog.List(ctx, port.AuditLogFilter{
    Type:  goauth.String("login.failed"),
    Limit: 50,
})

auth.Services.AuditLog is nil when audit logging isn't enabled — check that before calling into it directly the way the handler does, rather than relying on a panic.


Client

const { events, total } = await apiRequest(
  API_BASE, "GET", "/admin/audit-logs?event_type=login.failed&limit=50"
);

List one user's audit logs — GET /admin/users/{id}/audit-logs

Identical query params and response shape to the endpoint above — this one just forces target_user_id to the {id} in the path, overriding anything passed in the query string for that field.

curl

curl "https://api.myapp.com/admin/users/b3f1.../audit-logs?limit=50" \
  -H "Origin: https://myapp.com" \
  -b admin-cookies.txt

Programmatic (Go)

events, total, err := auth.Services.AuditLog.List(ctx, port.AuditLogFilter{
    TargetUserID: &userID,
    Limit:        50,
})

Client

const { events, total } = await apiRequest(API_BASE, "GET", `/admin/users/${userId}/audit-logs`);

Next

  • Admin — most of the actions that show up as admin.* events here
  • Routes — full param reference

On this page