Audit events

Every authentication and authorisation decision axess makes produces an audit event. The events are typed (AuthEvent, with one variant per kind of decision), they carry every field a compliance review needs, and they emit asynchronously so the authentication hot path does not block on the audit dispatch. This chapter covers the event catalogue, the fields each variant carries, the SOC alert thresholds that map events to operational signals, and the SIEM query patterns the events are designed to feed.

The chapter pairs with Audit pipeline, which covers how the events get from the application into the regulatory store and the analytics path.

What the events are for

Authentication is a security-sensitive operation, and security-sensitive operations need a defensible audit trail. Three audiences read the trail.

The first is the compliance auditor. A regulator (or an external auditor verifying compliance with a regulator's requirements) needs to verify that the application enforced the controls the regulation requires: that MFA was demanded where MFA was required, that lockouts fired when configured, that no cross-tenant access happened. The audit trail is what answers these questions.

The second is the incident responder. When something goes wrong (a user reports unauthorised access, a SIEM rule fires on an anomalous pattern, a breach is suspected), the responder needs to reconstruct what happened: which sessions were active, what authentications succeeded, what authorisations were granted. The audit trail is what supports the reconstruction.

The third is the operational dashboard. The application's running state is visible through the audit trail: how many logins succeed per hour, what fraction trigger lockouts, which tenants are active. The trail feeds the SIEM rules and the operational metrics.

The three audiences want different things from the same data, which is what drives the dual-stream design: a regulatory stream optimised for completeness and immutability, an analytics stream optimised for query latency and aggregation. Audit pipeline covers the streams; this chapter covers the events themselves.

The event catalogue

AuthEvent is the enum. The variants are stable across axess versions; new variants can be added (variants are appended, not renumbered), but existing variants do not change shape.

The catalogue is grouped by the operation that produces each event. The grouping below is for readability; the wire format is flat.

Authentication lifecycle

LoginStarted records the beginning of a login attempt. Fields: user_id, tenant_id, method_name, client_ip, user_agent, device_id (optional), timestamp.

FactorVerified records each successful factor verification. Fields: user_id, tenant_id, factor_kind, attempt_index, timestamp.

FactorFailed records each failed factor verification. Fields: user_id, tenant_id, factor_kind, attempt_index, failure_reason (string), timestamp.

LoginCompleted records a successful end-to-end login. Fields: user_id, tenant_id, method_name, factors_completed, session_id, device_id (optional), timestamp.

LoginFailed records a failed end-to-end login (any factor failed beyond retry, or a configuration error). Fields: user_id (optional, may be unknown), tenant_id, method_name (optional), failure_reason, timestamp.

Logout records a session ending. Fields: user_id, tenant_id, session_id, reason (user-initiated, admin-revoked, session-expired, cookie-fingerprint-mismatch), timestamp.

Lockout

LockoutTriggered records the per-user or per-tenant or per-IP lockout firing. Fields: scope (User, Tenant, IP), scope_value, until (optional unlock time), triggered_by_event (the preceding FactorFailed id), timestamp.

LockoutCleared records the lockout ending (either timing out or being administratively cleared). Fields: scope, scope_value, cleared_by (Timeout, Admin), timestamp.

Device identity

The six device events fire on the device-identity lifecycle covered in Device identity.

DeviceFirstSeen records a previously-unknown device fingerprint appearing. Fields: device_id (newly minted), user_id, tenant_id, fingerprint_features (the redacted feature set), timestamp.

DeviceTrustGranted records a device transitioning to Trusted. Fields: device_id, user_id, tenant_id, granted_by (User, Admin), timestamp.

DeviceRevoked records a device transitioning to Revoked. Fields: device_id, user_id, tenant_id, revoked_by (User, Admin, System), reason, timestamp.

DevicePurged records a device record being deleted (retention sweep). Fields: device_id, user_id, tenant_id, purged_at_age_days, timestamp.

DeviceBindingAdded records a new refresh-token binding to the device. Fields: device_id, user_id, tenant_id, refresh_token_id, timestamp.

DeviceFingerprintMismatch records a session presenting a fingerprint that does not match its bound device. Fields: device_id, user_id, tenant_id, session_id, policy_action (Warn, Reauth, Revoke), timestamp.

Authorisation

AuthzAllow records a successful Cedar evaluation. Fields: principal_uid, tenant_id, action, resource_uid, matched_policy_ids, timestamp.

AuthzDeny records a denied evaluation. Fields: principal_uid, tenant_id, action, resource_uid, matched_policy_ids (the policies that produced the deny, if any), denial_reason, timestamp.

AuthzEntityNotFound records an evaluation failure where the entity provider could not produce an entity the policies needed. Fields: principal_uid, missing_entity_uid, policy_id_referencing, timestamp.

Workload identity

The workload events fire on the workload-identity resolvers' decisions.

WorkloadAuthenticated records a successful workload authentication. Fields: workload_id, trust_domain, issuer, tenant_id, client_ip, timestamp.

WorkloadRejected records a failed workload authentication. Fields: attempted_workload_id (optional), attempted_trust_domain, failure_reason, client_ip, timestamp.

Tenant lifecycle

TenantCreated, TenantSuspended, TenantUnsuspended, and TenantDeleted record the tenant lifecycle from Multi-tenancy. Fields are uniform: tenant_id, operator_principal, reason, timestamp.

Delegated access

DelegatedConsentGranted records a user granting an application OBO access (the moment of OAuth consent). Fields: user_id, tenant_id, downstream, scopes, expires_at (optional), timestamp.

DelegatedCredentialUsed records a refresh of a stored OBO credential. Fields: user_id, tenant_id, downstream, timestamp.

DelegatedConsentRevoked records a user (or admin) revoking consent. Fields: user_id, tenant_id, downstream, revoked_by, timestamp.

DelegatedTokenExchanged records an RFC 8693 token exchange. Fields: subject_principal_uid, tenant_id, audience, scopes, timestamp.

Administrative

UserSuspended, UserUnsuspended, UserDeleted, PasswordReset (by admin), FactorReset (by admin), SessionInvalidated (by admin) cover the administrative operations. Fields name the target user, the operator principal, the reason, and the timestamp.

Emit cadence and fields

The events fire synchronously from the operation that produces them (the factor verification, the policy evaluation, the administrative action). The synchronous emit guarantees that an operation that succeeds has an event; an event without a corresponding operation is impossible.

The events are then handed to the audit pipeline (a sink trait the application configures), which dispatches them asynchronously. The pipeline's failure modes do not block the operation that produced the event; a sink that is slow or unavailable does not slow the application's hot path. The trade-off is that an event the sink fails to receive is lost, which is why the pipeline has a durable buffer in front of network-bound sinks. Audit pipeline covers the pipeline's reliability story.

Every event carries: a stable wire shape (so external systems can parse against a single schema), a per-event id (for deduplication and cross-referencing), a tenant id (so multi-tenant deployments can route events to per-tenant sinks), and a timestamp (in UTC, generated through the injected Clock trait for DST).

The fields specific to each variant are listed in the catalogue above. The complete schema lives in the axess-events crate documentation.

SOC alert thresholds

The events are designed to feed SOC (Security Operations Center) alerting. The thresholds below are starting points; tune to the specific deployment.

FactorFailed events from a single source IP at a rate above one per second indicate brute-forcing. The per-IP lockout (covered in Multi-tenancy §"Three-lever lockout") catches the worst cases at the application layer; the SIEM alert covers the rate even when individual events fall below the lockout threshold.

LockoutTriggered events at any rate are worth reviewing. A legitimate user occasionally mistypes their password and triggers a lockout; the rate should be a handful per day across a deployment. A spike indicates either a credential-stuffing attack or a configuration problem.

DeviceFingerprintMismatch events fire on every mismatch beyond the configured tolerance. Above a few per hour, either the tolerance is too tight (a legitimate-traffic issue, calibrate the tolerance) or there is a real attack (a stolen cookie being replayed). Investigate.

AuthzDeny events fire on every policy deny. The rate should correlate with legitimate user error (trying to access something they cannot); a spike correlates with either a policy misconfiguration (the rule is denying things it should permit) or with an attempt to find privilege-escalation holes.

WorkloadRejected events fire on every failed workload authentication. A clean deployment should see almost none of these; even a small rate indicates either a configuration problem or an attempted attack against the workload-identity surface.

DelegatedConsentGranted events fire on every user consent moment. The rate is informational; the events are most useful at the per-user level for "show me everything this user has granted the application permission to do."

SIEM query patterns

The events are designed for cheap aggregation in a SIEM. A sketch of useful queries:

-- Brute-force detection: top failing source IPs per minute.
SELECT
    client_ip,
    DATE_TRUNC('minute', timestamp) AS minute,
    COUNT(*) AS failures
FROM auth_events
WHERE event_type = 'FactorFailed'
  AND timestamp > NOW() - INTERVAL '1 hour'
GROUP BY 1, 2
ORDER BY failures DESC
LIMIT 20;
-- Lockout activity: users locked out in the last day.
SELECT
    scope_value AS user_id,
    COUNT(*) AS lockouts,
    MAX(timestamp) AS last_lockout
FROM auth_events
WHERE event_type = 'LockoutTriggered'
  AND scope = 'User'
  AND timestamp > NOW() - INTERVAL '1 day'
GROUP BY scope_value
ORDER BY lockouts DESC;
-- Step-up gaps: users hitting AuthzDeny on actions requiring stronger factors.
SELECT
    principal_uid,
    action,
    COUNT(*) AS denials
FROM auth_events
WHERE event_type = 'AuthzDeny'
  AND denial_reason LIKE '%factors_completed%'
  AND timestamp > NOW() - INTERVAL '1 day'
GROUP BY 1, 2
ORDER BY denials DESC;
-- Suspicious devices: fingerprint mismatches followed by trust grant.
SELECT
    mismatch.user_id,
    mismatch.device_id,
    mismatch.timestamp AS mismatch_at,
    grant.timestamp AS granted_at
FROM auth_events mismatch
JOIN auth_events grant
    ON mismatch.device_id = grant.device_id
WHERE mismatch.event_type = 'DeviceFingerprintMismatch'
  AND grant.event_type = 'DeviceTrustGranted'
  AND grant.timestamp > mismatch.timestamp
  AND grant.timestamp < mismatch.timestamp + INTERVAL '1 hour';

The queries assume a SQL-shaped SIEM (Splunk SPL, Sumo Logic LogReduce, ClickHouse). Adapt to the deployment's chosen tool.

Extending the catalogue

Applications occasionally need to record events that axess does not emit: a domain-specific operation that should appear in the same trail (a fund transfer, a configuration change, a sensitive read). The pattern is to extend the audit pipeline with a custom event type that rides the same machinery.

The mechanism is the AuthEventEnvelope wrapper: any Serialize + Deserialize type can be carried alongside the built-in AuthEvent variants as long as it implements AuditPayload. The application's domain events go into the envelope; the audit pipeline routes them to the same sinks as the axess events.

The trade-off is schema. A custom event type that the SIEM does not know about does not feed the dashboards or alerts. The typical pattern is to coordinate the schema with the SIEM team before adding new event types, so the dashboards and alerts can update in parallel.

What this enables

The audit catalogue is what makes axess defensible against regulatory and incident-response scrutiny. The events are typed, complete, asynchronous, and built for the SIEM tooling that production deployments already run. The catalogue itself is the reference; the Audit pipeline chapter covers how the events flow from the application to the storage layer.

Further reading

Audit pipeline covers the dual-stream architecture (regulatory plus analytics), the hot/cold retention tiering, and the reliability story for the asynchronous dispatch. Multi-tenancy covers the tenant-scoped routing of events. Cedar policy fundamentals covers the policy evaluator that produces the Authz* events. Security posture covers the GDPR and PCI-DSS posture for audit-event PII.