Skip to content

Audit event catalog ​

The OP emits structured audit events from a closed catalog defined in the op/audit package. The catalog is a vocabulary, not a promise that every constant fires: this page marks the signals emitted by the OP and the names reserved for a future implementation or the embedder's account/device plane. Each identifier has shape <area>.<verb> (or <area>.<verb>.<qualifier>) so SOC dashboards can pre-aggregate by area without parsing free-form messages.

One closed catalog, three destinations
Every code path in the OP emits from one closed catalog of audit events. Each emission carries its context in extras and reaches structured logs, whatever SOC pipeline consumes them, and the Prometheus counters.emitted fromlands inlogin / MFAfactor outcomes, step-upstoken / grantissue, rotate, revokeDCR / deviceregistration, approvalsAudit eventaudit.* — closed setcontext rides in extrasslog, as JSONone object per eventSOC / SIEMqueryable by event namePrometheuscounters by outcome
The catalog being closed is what makes the right-hand column possible. An alert can key on an event name because the set of names is fixed and versioned, not assembled from a format string at the call site.

Subscribing ​

go
op.New(
    /* required options */
    op.WithAuditLogger(slog.New(myJSONHandler)),
)

op.WithAuditLogger takes a *slog.Logger. Each event is recorded as a structured log entry. The event attribute contains the event identifier (e.g. "token.issued"), and msg is the short human-readable message. The canonical fields are optional top-level attributes: actor_id, client_id, session_id, request_id, ip, user_agent, and tag are omitted when unset. A non-empty extras group carries category-specific fields. Emission is synchronous on the caller's goroutine: the handler must not block on slow I/O or an unbounded queue, and it must be safe for concurrent calls.

If WithAuditLogger is not supplied, audit events fall through to the logger configured by WithLogger. If neither logger is supplied, audit emission is discarded.

Prometheus mirror

A curated subset of these events is mirrored onto Prometheus counters when WithPrometheus is configured. A single emission updates both the slog stream and the matching counter — there is no separate metrics emit step.

Enumerating the catalog in code ​

The event constants and the OP's own registry of audit events are public, in the github.com/libraz/go-oidc-provider/op/audit package (audit.LoginSuccess, audit.TokenIssued, and so on; the string values are the event names below). audit.Catalog() returns a copy of every event as []audit.EventDefinition:

go
import "github.com/libraz/go-oidc-provider/op/audit"

for _, def := range audit.Catalog() {
    // def.Event       — the audit event identifier, e.g. "token.issued"
    // def.MetricName  — the Prometheus counter it feeds; empty for audit-only events
    // def.MetricLabel — the bounded label value for category counters; often empty
}

Read it when you need the event set as data rather than as prose — provisioning SIEM rules, asserting in a test that a dashboard covers every event, or checking which events have a metric projection. Because the same registry drives both the in-tree emitters and the Prometheus bridge, event discovery and metric routing cannot drift apart.

Common attributes ​

Each emitted event carries audit and event. Canonical fields are optional and omitted when unset; extras is omitted when empty. With slog.JSONHandler, the record timestamp, level, and message also appear:

AttributeTypeNotes
auditstringalways "true"
eventstringevent identifier, such as "token.issued"
actor_idstringsubject identifier for a user-initiated event, or client_id for a client-initiated event; omitted when unset
client_idstringOAuth client_id; omitted when unset
session_idstringOP session identifier when applicable; omitted when unset
request_idstringrequest correlation identifier; omitted when unset
ipstringclient IP observed at the OP edge; omitted when unset
user_agentstringobserved User-Agent value; omitted when unset
tagstringoperator-supplied DCR tag; omitted when unset
extrasgroupcategory-specific fields (see each section); omitted when empty
timetimestampslog.Record.Time, from WithClock or the system clock
levelstringslog.Record.Level, such as INFO, WARN, or ERROR
msgstringslog.Record.Message, the short human-readable message

Event catalog ​

The catalog is regrouped by feature area. Each group opens with a short note on what the events mean for SOC and operations, followed by a table of event constant (the Go identifier in the op/audit package), when it fires or why it is reserved, severity hint (a starting point for routing — info for routine activity, warn for suspicious / failure conditions, alert for replay or store-fault signals), and the page that documents the surrounding behaviour. Rows explicitly labelled Reserved are not emitted by the OP; an absent reserved event is not evidence that the underlying action did not occur.

Provider lifecycle ​

startup.profile is the audit-stream anchor for an OP instance. It fires once after op.New has validated the configuration and before the provider is returned. Its extras cover two groups:

  • Declared — profiles, features, grants. profiles holds lowercase identifiers in declaration order: oauth21, oidc-core, fapi2-baseline, fapi2-message-signing, fapi-ciba. Without op.WithProfile it is ["oauth21"].
  • Resolved policy — signing_alg (ES256 or RS256), PKCE, PAR, nonce and state-or-nonce, sender-constraint, client-authentication, token-TTL, token-format, refresh grace and rotation, DPoP nonce, JAR, signed backchannel request, JARM, signed introspection, and the two insecure_*_for_dev flags.
Event constantWhen it firesSeverity hintLinked doc
audit.StartupProfilea validated provider startsinfoOptions reference

Account management ​

Reserved. These names cover out-of-band admin paths the OP does not host directly — provisioning, recovery, federation linking. The embedder may emit them through its audit sink; the OP itself does not.

Event constantWhen it firesSeverity hintLinked doc
audit.AccountCreatedReserved — user account provisioned by the embedderinfo—
audit.AccountDeletedReserved — user account removed by the embedderinfo—
audit.AccountEmailAddedReserved — email address added to an accountinfo—
audit.AccountEmailVerifiedReserved — email ownership proveninfo—
audit.AccountEmailRemovedReserved — email address removedinfo—
audit.AccountEmailSetPrimaryReserved — primary email changedinfo—
audit.AccountPasskeyRegisteredReserved — WebAuthn credential addedinfo—
audit.AccountPasskeyRemovedReserved — WebAuthn credential removedinfo—
audit.AccountTOTPEnabledReserved — TOTP enrollment completedinfo—
audit.AccountTOTPDisabledReserved — TOTP removedinfo—
audit.AccountPasswordChangedReserved — password reset or changedinfo—
audit.AccountRecoveryRegeneratedReserved — recovery batch reissuedinfo—
audit.RecoverySupportEscalationReserved — manual recovery or support override invokedwarn—
audit.AccountFederationLinkedReserved — external IdP credential linkedinfo—
audit.AccountFederationUnlinkedReserved — external IdP credential unlinkedinfo—

Login, MFA, step-up ​

login.* and mfa.* are emitted by the authenticator chain after a factor resolves. The first factor of a chain is reported as login.*; every later factor is mfa.*. That includes a step-up on a reused session, where the session's earlier methods already count as completed. login.failed and mfa.failed are the routine probing signals; sustained increases on either are the canonical credential-stuffing tell. Under WithAuthnLockoutStore, the failed guess that engages the lockout is reported as one of these failure events too, followed by lockout.engaged. audit.MFARequired, audit.StepUpRequired, and audit.StepUpSuccess are Reserved: the OP does not emit them. Step-up is decided by the resource server and an ordinary authorization request reaches the OP.

Event constantWhen it firesSeverity hintLinked doc
audit.LoginSuccessprimary credential verified, subject boundinfoUse case: MFA / step-up
audit.LoginFailedprimary credential rejectedwarnUse case: MFA / step-up
audit.LockoutStalledcross-factor lockout CAS contention abandoned a failed attempt without counting it, so the subject's failure budget did not advancealertUse case: MFA / step-up
audit.LockoutEngageda failed attempt newly engaged the cross-factor brute-force lock (password, TOTP, email OTP or recovery code); fires once, with the subject as ActorID, and not again for attempts made while the lock runs. A locked password attempt is answered exactly like a wrong password, so this event is the operator's signal that the user cannot log inwarnUse case: MFA / step-up
audit.StepBeginFailed (login.step_begin_failed)a login-flow step could not begin, so a factor that refuses before prompting still leaves a record. extras carry step_kind and reason (not_enrolled, aborted, locked, not_enrolled_skipped, error); actor_id is the subject when one is bound; a request the client cancelled is not recordedwarn; info for not_enrolled_skipped (a one-of step passed over because the subject has none of its factors)Use case: MFA / step-up
audit.MFARequiredReserved — catalog vocabulary; the OP does not emit a separate requirement eventinfoUse case: MFA / step-up
audit.MFASuccesssecond factor acceptedinfoUse case: MFA / step-up
audit.MFAFailedsecond factor rejectedwarnUse case: MFA / step-up
audit.StepUpRequiredReserved — resource-server decision; the OP does not emit itinfoUse case: MFA / step-up
audit.StepUpSuccessReserved — resource-server outcome; the OP does not emit itinfoUse case: MFA / step-up
extras
  • factor — password, passkey, totp, email_otp, captcha, recovery_code, or a user-defined Step kind

aal, acr and amr_values are not carried by these events.

audit.ConsentGranted and audit.ConsentGrantedFirstParty are emitted by the consent prompt and first-party fast-path. The other consent constants are Reserved: incremental consent is recorded as ordinary consent.granted, reuse of an existing grant has no audit event, and grant withdrawal uses Grant Management's grant_management.revoked.

Event constantWhen it firesSeverity hintLinked doc
audit.ConsentGranteduser clicked consent in the promptinfoConcepts: consent
audit.ConsentGrantedFirstPartyfirst-party auto-consent applied (no prompt)infoUse case: first-party consent
audit.ConsentGrantedDeltaReserved — incremental request is emitted as ordinary audit.ConsentGrantedinfoConcepts: consent
audit.ConsentSkippedExistingReserved — reuse of a stored consent is not emittedinfoConcepts: consent
audit.ConsentRevokedReserved — Grant Management withdrawal emits audit.GrantManagementRevokedinfoConcepts: consent
extras
  • grant_id — the grant the consent was recorded under
  • scope — the scopes the code was minted with
  • completion_id — irreversible fingerprint of the authorization code the browser is about to carry to the RP

Code and token lifecycle ​

Fire from the authorize-code path and the token endpoint. The replay-detection events (code.replay_detected, refresh.replay_detected) and the substore-fault events (token.revoke_failed, refresh.chain_revoke_failed, refresh.grant_revoke_failed) are the high-signal alerts in this group; the rest is steady-state lifecycle telemetry.

Event constantWhen it firesSeverity hintLinked doc
audit.CodeIssuedauthorization code minted; exactly once for each issued code, including interactive and silent pathsinfoConcepts: authorization code + PKCE
audit.CodeConsumedcode redeemed at /tokeninfoConcepts: authorization code + PKCE
audit.CodeReplayDetectedcode presented twice — chain revokedalertConcepts: authorization code + PKCE
audit.TokenIssueda first refresh token is persisted for a grant (the event is not a counter for every access token)infoConcepts: tokens
audit.TokenRefreshedrefresh token rotated and the rotation is durable; a refresh that lost a concurrent presentation at commit, or rolled back, records nothinginfoConcepts: refresh tokens
audit.TokenRevoked/revoke revoked a token; also recorded at warn level when /token refuses a redemption because the grant's subject no longer existsinfoConcepts: tokens
audit.TokenRevokeFailedrevocation attempt observed a substore fault; wire still 200 per RFC 7009 §2.2alertConcepts: tokens
audit.RefreshReplayDetectedrefresh token presented past rotation grace — chain revokedalertConcepts: refresh tokens
audit.RefreshChainRevokeFailedcascade revoke from a replay observed a substore faultalertConcepts: refresh tokens
audit.RefreshGrantRevokeFailedgrant-tombstone write failed during refresh-rotation cascadealertConcepts: refresh tokens
audit.RefreshPriorAccessTokenRevokeFailedrefresh rotation could not revoke the prior access token; extras identify whether retry is possiblealertConcepts: refresh tokens

code_id and completion_id are irreversible fingerprints of the authorization-code identifier. They are correlation values, not raw authorization codes or internal identifiers. code.issued is emitted exactly once for each issued code.

extras
  • refresh_origin — the typed origin recorded on the refresh chain: authorization_code, device_code, ciba, or custom_grant; an unknown or missing origin is projected as unknown by the metrics bridge. This is the grant that actually minted the refresh chain, not an arbitrary request grant_type label.
  • grant_id — on token.issued, token.refreshed, token.revoked and code.*: the grant the credential belongs to; on token.revoke_failed (token endpoint) and refresh.grant_revoke_failed: the grant whose teardown failed
  • scope — on code.issued and code.consumed: the scope of the code
  • code_id — on code.*: irreversible fingerprint of the authorization code
  • offline_access — on token.issued and token.refreshed: bool; true on an offline_access chain
  • ttl_bucket — on token.issued and token.refreshed: which refresh-token lifetime applied
  • surface — on token.revoked: jwt_access_token, opaque_access_token, refresh_chain, or refresh_grant (the chain root could not be resolved, so /revoke fell back to revoking the grant's refresh tokens); subject_deprovisioned for a refused redemption. On token.revoke_failed: which step observed the fault, for example refresh_chain_walk, refresh_chain, refresh_grant, opaque_access_token_lookup; the token endpoint prefixes the cascade name, as in code_replay_jwt_access_tokens
  • jti — on token.revoked for a JWT access token
  • reason — on refresh.chain_revoke_failed / refresh.grant_revoke_failed: stringified error from the substore
  • err — on token.revoke_failed: stringified error from the substore
  • failure_stage, retryable — on refresh.prior_access_token_revoke_failed

Sessions and logout ​

The session and logout rows below are emitted by the session manager, /end_session, and the detached Back-Channel Logout coordinator. audit.LogoutRPInitiated is Reserved: /end_session records the resulting session and delivery outcomes, not a separate marker that an RP asked.

Event constantWhen it firesSeverity hintLinked doc
audit.SessionCreatednew browser session minted (post-login)infoConcepts: sessions and logout
audit.SessionDestroyedsession deleted (logout, expiry, eviction)infoConcepts: sessions and logout
audit.SessionAlreadyAbsentlogout targeted a session already absent or expiredinfoConcepts: sessions and logout
audit.SessionDestroyFailedsession deletion hit a store failure; the request returns static 503 and preserves statealertConcepts: sessions and logout
audit.LogoutClientLookupFailedclient-registry lookup failed with a store error while resolving an RP-initiated logout; the wire response remains genericalertConcepts: sessions and logout
audit.LogoutRPInitiatedReserved — no separate RP-initiated marker is emittedinfoConcepts: sessions and logout
audit.LogoutTokenRevokeFailedlogout-token or logout cascade revocation hit a store failurealertConcepts: sessions and logout
audit.LogoutBackChannelDeliveredBack-Channel logout token delivered to RPinfoUse case: Back-Channel Logout
audit.LogoutBackChannelFailedBack-Channel delivery failed (HTTP error / timeout / verification)warnUse case: Back-Channel Logout
audit.LogoutBackChannelResolveFailedgrant-based target resolution failedalertUse case: Back-Channel Logout
audit.LogoutBackChannelOverflowbounded target resolution overflowedwarnUse case: Back-Channel Logout
audit.BCLNoSessionsForSubjecta session-bearing /end_session notice resolved zero grant-derived RP targets (no RPs to notify)infoUse case: Back-Channel Logout
bcl.no_sessions_for_subject rationale

This event is emitted only when Notice.SessionID is present and grant-based audience resolution returns zero RP targets; it does not mean that the coordinator walked SessionStore or that a session was evicted. If SessionStore loses the row before /end_session can snapshot it, the handler does not start detached fan-out and this event is not emitted. INFO-level by design: zero grant-derived targets can be an ordinary no-op, so SOC tooling should alert on elevated rates rather than per-event. The event includes the configured WithSessionDurabilityPosture as context for trigger/snapshot loss versus a no-target outcome.

Defensive signals ​

The OP emits the PKCE, redirect-mismatch, CORS, DPoP (when the opt-in loose verifier is used), and retired-key signals below. Rate-limit names are Reserved for the embedder. The legacy-algorithm name is Reserved catalog vocabulary: the OP rejects those requests but does not emit the audit event.

Event constantWhen it firesSeverity hintLinked doc
audit.RateLimitExceededrequest rejected by rate limiter — embedder-emitted vocabulary; the library does not implement a generic per-IP / per-endpoint HTTP throttlewarn—
audit.RateLimitBypassedrate-limit bypass token consumed (operator override) — embedder-emitted vocabulary; same scoping as audit.RateLimitExceededwarn—
audit.PKCEViolationa code_verifier failed at /token; extras carry endpoint and reason (verifier_mismatch, verifier_format, challenge_method_unsupported), never the verifierwarnConcepts: authorization code + PKCE
audit.RedirectURIMismatch/authorize received an unregistered redirect_uri, or /token received one that differs from the authorization request; extras carry endpoint and the presented redirect_uri. The /token answer stays a generic invalid_grant, so this event is where the cause showswarnConcepts: redirect URI
audit.AlgLegacyUsedReserved — no OP path emits a legacy-algorithm telemetry eventwarnConcepts: JOSE basics
audit.CORSPreflightAlloweda CORS preflight matched the strict allow-listinfoUse case: CORS for SPA
audit.DPoPLooseMethodCaseAdmittedConditional — emitted only by a verifier configured with the loose method-case bridge; the standard op.New path is strictinfoConcepts: DPoP
audit.KeyRetiredKidPresentedrequest presented a kid past the JWKS grace window — verifier rejectedwarnOperations: key rotation

Interaction ​

The OP emits a render-failure signal only when the interaction driver fails before committing a response. If the driver has already committed the response, the OP cannot replace it and emits no duplicate event.

Event constantWhen it firesSeverity hintLinked doc
audit.InteractionRenderFailedthe interaction driver's render failed before response commitment; the OP serves a generic server_error pagealertUse case: SPA custom interaction

Introspection ​

Fire from /introspect when client authentication rejects or an opaque access-token / refresh-token store lookup fails. A clean inactive miss emits no error event. For a non-NotFound lookup fault, the audit event surfaces the outage while the RFC 7662 wire response remains HTTP 200 with {"active":false}; client-authentication failures retain the canonical error response.

Event constantWhen it firesSeverity hintLinked doc
audit.IntrospectionError/introspect rejected inbound client credentials or observed a non-NotFound opaque token-store lookup faultwarn / alertOperations: observability

Client authentication ​

Fire from /token, /par, /device_authorization, and /bc-authorize whenever client authentication rejects. The wire response stays at the canonical invalid_client; this event exposes the attempted client_id and a short reason code so SOC tooling can spot probing. /introspect uses audit.IntrospectionError instead, and /revoke does not emit this event.

Event constantWhen it firesSeverity hintLinked doc
audit.ClientAuthnFailure/token, /par, /device_authorization, or /bc-authorize rejected the client (wrong secret, expired assertion, alg mismatch, missing private_key_jwt)warnConcepts: client types
audit.ClientAuthnPreviousSecret (client_authn.previous_secret)a client authenticated with client_secret_basic or client_secret_post using its previous secret inside the rotation window; extras["method"] names the method and the secret is never attached. Use it to find clients that still have to switch before the window closeswarnConcepts: client types

Client ID Metadata Documents ​

Experimental, like op.WithClientIDMetadataDocuments. These events fire when the OP resolves a URL-form client_id (see MCP authorization server).

Event constantWhen it firesSeverity hintLinked doc
audit.CIMDFetched (cimd.fetched)a client ID metadata document was fetched and validatedinfoUse case: MCP authorization server
audit.CIMDFetchFailed (cimd.fetch_failed)fetching or validating a document failed; extras["reason"] is one of status, network, too_large, content_type, json, validation, and extras["error_code"] carries the validation error code for validationwarnUse case: MCP authorization server
audit.CIMDClientDenied (cimd.client_denied)the ClientIDMetadataDocuments.Allow hook refused a client_id URLwarnUse case: MCP authorization server

Dynamic Client Registration ​

Fire from /register and /register/{client_id}. RAT and IAT failure events are the canonical probing signal under DCR.

Event constantWhen it firesSeverity hintLinked doc
audit.DCRIATConsumedInitial Access Token redeemedinfoUse case: Dynamic Client Registration
audit.DCRIATExpiredIAT presented past TTLwarnUse case: Dynamic Client Registration
audit.DCRIATInvalidIAT signature / format invalidwarnUse case: Dynamic Client Registration
audit.DCROpenRegistrationUsedopen (no-IAT) registration acceptedinfoUse case: Dynamic Client Registration
audit.DCRClientRegisterednew client createdinfoUse case: Dynamic Client Registration
audit.DCRClientMetadataReadRAT-bearing GET against /register/{client_id}infoUse case: Dynamic Client Registration
audit.DCRClientMetadataUpdatedRAT-bearing PUT against /register/{client_id}infoUse case: Dynamic Client Registration
audit.DCRClientDeletedRAT-bearing DELETE against /register/{client_id}infoUse case: Dynamic Client Registration
audit.DCRRATInvalidRegistration Access Token rejectedwarnUse case: Dynamic Client Registration
audit.DCRMetadataValidationmetadata payload failed policywarnUse case: Dynamic Client Registration
audit.DCRCascadeRefreshRevokeFailedclient deletion could not revoke that client's refresh-token rowsalertUse case: Dynamic Client Registration
audit.DCRCascadeGrantRevokeFailedclient deletion could not revoke that client's grantsalertUse case: Dynamic Client Registration
audit.DCRCascadeAccessTokenRevokeFailedclient deletion could not revoke that client's JWT access-token rowsalertUse case: Dynamic Client Registration
audit.DCRCascadeOpaqueAccessTokenRevokeFailedclient deletion could not revoke that client's opaque access-token rowsalertUse case: Dynamic Client Registration

Device Code (RFC 8628) ​

Fire from /device_authorization, the embedder's verification ceremony helpers in op/devicecodekit, and the token-endpoint device-code grant. device_code.verification.user_code_brute_force and device_code.token.slow_down are the canonical poll-abuse signals.

Event constantWhen it firesSeverity hintLinked doc
audit.DeviceAuthorizationIssued/device_authorization returned a fresh device_code + user_code pairinfoConcepts: device code
audit.DeviceAuthorizationRejected/device_authorization rejected the inbound request (unknown client, scope refusal, etc.)warnConcepts: device code
audit.DeviceAuthorizationUnboundRejectedDPoP / mTLS proof was required but absent from /device_authorizationwarnConcepts: sender-constrained tokens
audit.DeviceCodeVerificationApprovedembedder's verification page reported the user approved the pending codeinfoUse case: device code
audit.DeviceCodeVerificationDeniedembedder's verification page reported the user denied the pending code (or the per-record brute-force gate locked the row out)warnUse case: device code
audit.DeviceCodeUserCodeBruteForcea user_code submission missed; counter incremented (lockout fires after devicecodekit.MaxUserCodeStrikes, default 5)alertUse case: device code
audit.DeviceCodeTokenIssued/token minted a token for an approved device authorizationinfoUse case: device code
audit.DeviceCodeTokenRejected/token rejected the device-code grant (access_denied, expired_token, etc.)warnUse case: device code
audit.DeviceCodeTokenSlowDown/token returned slow_down; 5 seconds were added to the substore row's polling intervalwarnUse case: device code
audit.DeviceCodeRevokedop/devicecodekit.Revoke flipped the row to denied; the helper cascades under the grant the device flow allocated; extras report each surface it attempted (cascade_complete, revoked_access_tokens, grant_tombstone_written, revoked_opaque_access_tokens, refresh_token_cascade_complete), and grant_record_deleted when the optional Deps.Grants is setinfoUse case: device code
audit.DeviceCodePollObservationFailedpersisting the LastPolledAt stamp on a poll faulted; decision still proceeds (best-effort observability)warnUse case: device code

CIBA ​

Fire from /bc-authorize and the token-endpoint CIBA grant. ciba.poll_abuse.lockout is the canonical client-misbehaviour signal. The authentication-device approval and denial names are Reserved: the embedder calls CIBARequestStore.Approve or Deny directly, outside an OP emission point.

Event constantWhen it firesSeverity hintLinked doc
audit.CIBAAuthorizationIssued/bc-authorize returned a fresh auth_req_idinfoConcepts: CIBA
audit.CIBAAuthorizationRejected/bc-authorize rejected the inbound request (unknown user, hint resolver failed, scope refusal)warnConcepts: CIBA
audit.CIBAAuthorizationUnboundRejectedDPoP / mTLS proof was required but absent from /bc-authorizewarnConcepts: sender-constrained tokens
audit.CIBAAuthDeviceApprovedReserved — embedder authentication-device callback; the OP does not observe the direct Approve callinfoUse case: CIBA
audit.CIBAAuthDeviceDeniedReserved — embedder authentication-device callback; the OP does not observe the direct Deny callwarnUse case: CIBA
audit.CIBAPollAbuseLockoutpoll cadence sustained well below the negotiated interval; the request was locked outalertUse case: CIBA
audit.CIBATokenIssued/token minted a token for an approved CIBA request; the redemption records a grant, so later requests for the same scopes skip the consent screeninfoUse case: CIBA
audit.CIBATokenRejected/token rejected the CIBA grant (access_denied, expired_token, authorization_pending)warnUse case: CIBA
audit.CIBATokenSlowDown/token returned slow_down because the client polled faster than the negotiated intervalwarnUse case: CIBA
audit.CIBAPollObservationFailedpersisting the LastPolledAt stamp on a poll faulted; decision still proceeds (best-effort observability)warnUse case: CIBA

Token Exchange (RFC 8693) ​

Fire from the in-tree WithTokenExchangeGrant handler. requested is emitted at handler entry, before parsing and structural validation, so structurally rejected attempts are included. Every successful exchange then emits requested + granted, with granted and refresh_issued recorded only once issuance has succeeded; an issuance failure records custom_grant.failed instead. Later rejections emit requested + one of the failure-class events. policy_denied, scope_inflation_blocked, and audience_blocked are the policy-decision signals SOC dashboards usually want.

Event constantWhen it firesSeverity hintLinked doc
audit.TokenExchangeRequested/token dispatched an RFC 8693 attempt to the exchange handler, before parsing and structural validationinfoConcepts: token exchange
audit.TokenExchangeGrantedexchange admitted and issuance succeeded; recorded only after the tokens are mintedinfoUse case: token exchange
audit.TokenExchangePolicyDeniedthe embedder-supplied TokenExchangePolicy returned a denywarnUse case: token exchange
audit.TokenExchangePolicyErrorthe policy returned a non-deny error (transient infrastructure failure)warnUse case: token exchange
audit.TokenExchangeScopeInflationBlockedrequested scope exceeded the subject_token's scope or the client's allow-listwarnUse case: token exchange
audit.TokenExchangeAudienceBlockedrequested audience was not in the policy's allow-listwarnUse case: token exchange
audit.TokenExchangeTTLCappedissued TTL was clipped against (handler request, subject_token remaining, global ceiling)infoUse case: token exchange
audit.TokenExchangeActChainTooDeepthe subject_token's nested act chain exceeded the five-level depth limit; checked before the policy runswarnUse case: token exchange
audit.TokenExchangeEmptyScopeRejectedexchange requested a non-empty scope subset that resolved to the empty setwarnUse case: token exchange
audit.TokenExchangeActorEqualsSubjectactor_token resolved to the same subject as subject_token; the exchange is rejected as invalid_requestinfoUse case: token exchange
audit.TokenExchangeSubjectTokenExternalsubject_token did not resolve in the local registry; treated as opaque externalinfoUse case: token exchange
audit.TokenExchangeActorTokenExternalactor_token did not resolve in the local registry; treated as opaque externalinfoUse case: token exchange
audit.TokenExchangeSubjectTokenInvalidsubject_token verification failed (signature / TTL / cnf mismatch)warnUse case: token exchange
audit.TokenExchangeSubjectTokenRegistryErrorregistry lookup observed a non-NotFound fault (transient outage); wire stays invalid_grantalertUse case: token exchange
audit.TokenExchangeRefreshIssuedexchange opted into refresh issuance (IssueRefreshToken=op.PtrBool(true)) and the refresh token was issuedinfoUse case: token exchange
audit.TokenExchangeSelfExchangeno actor_token was sent and the subject_token was issued to the calling client itself (a self-exchange; no act claim is added)infoUse case: token exchange
audit.TokenExchangeIDJAGIssued (token_exchange.idjag_issued)an ID-JAG was issued in answer to an ID-JAG token exchange, recorded once the response is written. extras carries audience, aud_client_id, granted_scope, resource, ttl_seconds and the ID-JAG jtiinfoUse case: token exchange

JWT bearer grant (RFC 7523) ​

These events come from the urn:ietf:params:oauth:grant-type:jwt-bearer grant at /token, for both the plain and the ID-JAG profile. The rejection reason is the verifier stage that failed (parse, issuer, typ, signature, claims, idjag, replay) or a token-endpoint check (for example public_client, missing_assertion, client_not_allowed, scope, resource, binding, unknown_subject, key_source_unavailable, verifier_fault).

Event constantWhen it firesSeverity hintLinked doc
audit.JWTBearerGranted (jwt_bearer.granted)an assertion was redeemed and an access token issued; extras carries the assertion issuer and the profile, and the actor is the resolved subjectinfoUse case: token exchange
audit.JWTBearerRejected (jwt_bearer.rejected)a redemption was refused; extras carries reason and, once the assertion's iss was read, issuerinfoUse case: token exchange

Custom Grant ​

The dispatcher emits custom_grant.requested at dispatch entry for every attempt that reaches a registered custom handler, including successes and attempts that fail a gate before the handler runs. An unknown or unregistered grant_type returns before custom-grant dispatch and emits no custom-grant audit event. The dispatcher emits one custom_grant.failed at the single failure exit; a handler panic is recovered and produces that same one failed event with panic details in extras.

EventMeaningLevelSee
audit.CustomGrantRequested (custom_grant.requested)every dispatch attempt that reaches a registered custom handler, including pre-handler gate failuresinfoUse case: custom grant
audit.CustomGrantFailed (custom_grant.failed)custom grant dispatch or handler failed; emitted once per failed attempt, including a recovered panicwarnUse case: custom grant
audit.CustomGrantRefreshDropped (custom_grant.refresh_dropped)handler asked for a refresh token with IssueRefreshToken, but the client is not registered for refresh_token; the access-token response still succeedsinfoUse case: custom grant

Grant management ​

These events come from the OAuth 2.0 Grant Management endpoint. The failure signal is emitted when the authenticated revoke request cannot complete its grant cascade.

Event constantWhen it firesSeverity hintLinked doc
audit.GrantManagementRevokeda DELETE to the grant management endpoint revoked a grant and cascaded its tokensinfoUse case: grant management
audit.GrantManagementRevokeFaileda DELETE was authenticated but the grant-revoke side effect failedalertUse case: grant management

Shared Signals ​

These events come from the Shared Signals transmitter enabled by op.WithSharedSignals (see Shared Signals). None of them updates a metric. Delivery events carry stream_id, jti, kind and event_type in extras.

Event constantWhen it firesSeverity hintLinked doc
audit.SSFStreamCreated (ssf.stream.created)a receiver created its streaminfoOperations: Shared Signals
audit.SSFStreamUpdated (ssf.stream.updated)a receiver updated or replaced its stream, or changed its statusinfoOperations: Shared Signals
audit.SSFStreamDeleted (ssf.stream.deleted)a receiver deleted its stream; every event still queued for it is droppedinfoOperations: Shared Signals
audit.SSFSETEnqueued (ssf.set.enqueued)a Security Event Token was queued for a stream. extras carry stream_id, jti, event_type, delivery and heldinfoOperations: Shared Signals
audit.SSFEnqueueFailed (ssf.enqueue_failed)an event could not be queued for its receivers. The operation that raised it (a logout, RevokeSubject) is not failed. extras["reason"] names the cause; fanout_capacity means the subject had more receivers than one fan-out readsalertOperations: Shared Signals
audit.SSFPushDelivered (ssf.push_delivered)a receiver accepted a pushed SET with 202infoOperations: Shared Signals
audit.SSFPushRejected (ssf.push_rejected)a receiver answered a pushed SET with 400; the SET is dropped without a retry. extras["err"] carries the receiver's error code when it sent onewarnOperations: Shared Signals
audit.SSFPushRetried (ssf.push_retried)a push failed transiently (5xx, 429, network) and was rescheduled. extras carry attempts and next_attempt_atwarnOperations: Shared Signals
audit.SSFPushAbandoned (ssf.push_abandoned)a SET was dropped after its last retry, or because the stream's authorization header could no longer be unsealed (extras["reason"] = "seal")alertOperations: Shared Signals
audit.SSFPollSETError (ssf.poll_set_error)a polling receiver reported a SET it could not process (RFC 8936 setErrs); the SET is dropped. extras carry err and descriptionwarnOperations: Shared Signals

Stability ​

Audit event names are part of the public API surface:

  • New events MAY be added in any minor release.
  • Existing event names are renamed only in a major release with a deprecation notice.
  • The events marked Experimental in godoc (audit.TokenExchangeIDJAGIssued and the audit.CIMD* events) follow the IETF drafts they track and MAY change in a minor release.

Pin your dashboards on the names; do not rely on the order or the extras shape for any field not documented here.

Verifying this list ​

sh
git clone https://github.com/libraz/go-oidc-provider.git
cd go-oidc-provider
audit_catalog_tmp=$(mktemp ./audit-catalog-XXXXXX.go)
trap 'rm -f "$audit_catalog_tmp"' EXIT
cat >"$audit_catalog_tmp" <<'EOF'
package main

import (
  "fmt"
  "github.com/libraz/go-oidc-provider/op/audit"
)

func main() {
  for _, def := range audit.Catalog() {
    fmt.Println(def.Event)
  }
}
EOF
go run "$audit_catalog_tmp"

This calls the public audit.Catalog() API and prints the closed catalog this page mirrors.