Skip to content

Audit event catalog

The OP emits structured audit events from a closed catalog defined in op/audit.go. Each event is a stable string identifier of shape <area>.<verb> (or <area>.<verb>.<qualifier>) so SOC dashboards can pre-aggregate by area without parsing free-form messages.

Subscribing

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

op.WithAuditLogger takes a *slog.Logger. Each event records as a structured log entry where the msg is the event identifier (e.g. "token.issued") and the attributes carry the request-id, subject, client-id, and an extras group with category-specific fields.

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.

Common attributes

Every event carries:

AttributeTypeNotes
request_idstringper-request identifier (propagated via X-Request-ID if set)
subjectstringOIDC sub of the affected user; empty for pre-authn events
client_idstringOAuth client_id; empty for account-management events
extrasgroupcategory-specific fields (see each section)

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 op/audit.go), when it fires, 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.

Account management

Most of these fire from out-of-band admin paths the OP does not host directly — provisioning, recovery, federation linking. The catalog exists so a single subscription point covers SOC dashboards regardless of where the actual write originates.

Event constantWhen it firesSeverity hintLinked doc
AuditAccountCreateduser account provisionedinfo
AuditAccountDeleteduser account removedinfo
AuditAccountEmailAddedemail address added to accountinfo
AuditAccountEmailVerifiedemail ownership proven (link / OTP)info
AuditAccountEmailRemovedemail address removedinfo
AuditAccountEmailSetPrimaryprimary email changedinfo
AuditAccountPasskeyRegisteredWebAuthn credential addedinfo
AuditAccountPasskeyRemovedWebAuthn credential removedinfo
AuditAccountTOTPEnabledTOTP enrollment completedinfo
AuditAccountTOTPDisabledTOTP removedinfo
AuditAccountPasswordChangedpassword reset / changedinfo
AuditAccountRecoveryRegeneratedrecovery batch reissuedinfo
AuditRecoverySupportEscalationmanual recovery / support override invokedwarn
AuditAccountFederationLinkedexternal IdP credential linkedinfo
AuditAccountFederationUnlinkedexternal IdP credential unlinkedinfo

Login, MFA, step-up

Fire from the authenticator chain after each factor resolves. login.failed and mfa.failed are the routine probing signals; sustained increases on either are the canonical credential-stuffing tell. step_up.required rates show how often RPs are asking the user to climb to a higher acr_values.

Event constantWhen it firesSeverity hintLinked doc
AuditLoginSuccessprimary credential verified, subject boundinfoUse case: MFA / step-up
AuditLoginFailedprimary credential rejectedwarnUse case: MFA / step-up
AuditMFARequiredsession AAL below requirement; second factor demandedinfoUse case: MFA / step-up
AuditMFASuccesssecond factor acceptedinfoUse case: MFA / step-up
AuditMFAFailedsecond factor rejectedwarnUse case: MFA / step-up
AuditStepUpRequiredRP requested higher acr_values; user re-promptedinfoUse case: MFA / step-up
AuditStepUpSuccessstep-up factor acceptedinfoUse case: MFA / step-up
extras
  • factorpassword, passkey, totp, email_otp, captcha, recovery_code, or a user-defined Step kind
  • aal — assurance level the factor raised the session to
  • amr_values — RFC 8176 §2 codes contributed to the amr claim

Fire from the consent prompt and the first-party fast-path. consent.granted.delta is the signal that an existing grant did not cover the new request and the user re-confirmed; consent.revoked is the user-driven cleanup path SOC dashboards usually want to chart against token.revoked.

Event constantWhen it firesSeverity hintLinked doc
AuditConsentGranteduser clicked consent in the promptinfoConcepts: consent
AuditConsentGrantedFirstPartyfirst-party auto-consent applied (no prompt)infoUse case: first-party consent
AuditConsentGrantedDeltadelta-consent triggered re-prompt for newly requested sensitive scopeinfoConcepts: consent
AuditConsentSkippedExistingexisting grant already covered the request; no promptinfoConcepts: consent
AuditConsentRevokeduser revoked a prior grantinfoConcepts: consent
extras
  • scopes_granted, scopes_requested — string slices
  • audience — when scope is per-audience

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
AuditCodeIssuedauthorization code mintedinfoConcepts: authorization code + PKCE
AuditCodeConsumedcode redeemed at /tokeninfoConcepts: authorization code + PKCE
AuditCodeReplayDetectedcode presented twice — chain revokedalertConcepts: authorization code + PKCE
AuditTokenIssuedaccess token + (optional) refresh + (optional) id_token issuedinfoConcepts: tokens
AuditTokenRefreshedrefresh token rotated; new access token mintedinfoConcepts: refresh tokens
AuditTokenRevokedtoken revoked via /revoke or grant cascadeinfoConcepts: tokens
AuditTokenRevokeFailedrevocation attempt observed a substore fault; wire still 200 per RFC 7009 §2.2alertConcepts: tokens
AuditRefreshReplayDetectedrefresh token presented past rotation grace — chain revokedalertConcepts: refresh tokens
AuditRefreshChainRevokeFailedcascade revoke from a replay observed a substore faultalertConcepts: refresh tokens
AuditRefreshGrantRevokeFailedgrant-tombstone write failed during refresh-rotation cascadealertConcepts: refresh tokens
extras
  • grant_typeauthorization_code, refresh_token, client_credentials
  • formatjwt or opaque for the access token
  • offline_access — bool; true on offline_access chain
  • cnfdpop_jkt or mtls_x5t#S256 when sender-bound
  • surface — on token.revoke_failed: which call site observed the fault. /revoke emits jwt_access_token, refresh_chain, or opaque_access_token; the token endpoint emits code_replay_jwt_access_tokens when the AT cascade after authorization-code replay errors out
  • grant_id — on token.revoke_failed (token endpoint) and refresh.grant_revoke_failed: the grant whose tombstone write failed
  • 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

Sessions and logout

Fire from the session store and the logout endpoints. bcl.no_sessions_for_subject is the volatile-session signal documented below; the back-channel delivery events are the SOC pivot when an RP fails to honour logout.

Event constantWhen it firesSeverity hintLinked doc
AuditSessionCreatednew browser session minted (post-login)infoConcepts: sessions and logout
AuditSessionDestroyedsession deleted (logout, expiry, eviction)infoConcepts: sessions and logout
AuditLogoutRPInitiatedRP-Initiated Logout invokedinfoConcepts: sessions and logout
AuditLogoutBackChannelDeliveredBack-Channel logout token delivered to RPinfoUse case: Back-Channel Logout
AuditLogoutBackChannelFailedBack-Channel delivery failed (HTTP error / timeout / verification)warnUse case: Back-Channel Logout
AuditBCLNoSessionsForSubject/end_session named a subject but no sessions resolvedinfoUse case: Back-Channel Logout
bcl.no_sessions_for_subject rationale

Under a volatile SessionStore (Redis without persistence, in-memory under maxmemory eviction) this event is the signal that a session was evicted between establishment and logout — narrowing OIDC Back-Channel Logout 1.0 §2.7's best-effort delivery floor to zero. INFO-level by design: under volatile placement the gap is expected; SOC tooling should alert on elevated rates rather than per-event. The event includes the configured WithSessionDurabilityPosture so dashboards can split "expected gap under volatile" from "unexpected gap under durable".

Defensive signals

Fire from request-validation paths that detect abuse signals or operator-visible policy hits. These are the events that feed automated abuse-mitigation pipelines.

Event constantWhen it firesSeverity hintLinked doc
AuditRateLimitExceededrequest rejected by rate limiter — embedder-emitted vocabulary; the library does not implement a generic per-IP / per-endpoint HTTP throttlewarn
AuditRateLimitBypassedrate-limit bypass token consumed (operator override) — embedder-emitted vocabulary; same scoping as AuditRateLimitExceededwarn
AuditPKCEViolationPKCE verifier mismatch / plain rejected / missing on FAPIalertConcepts: authorization code + PKCE
AuditRedirectURIMismatchredirect URI did not match the registered listwarnConcepts: redirect URI
AuditAlgLegacyUseda legacy alg path was reached (telemetry; rejected by the verifier)warnConcepts: JOSE basics
AuditCORSPreflightAlloweda CORS preflight matched the strict allowlistinfoUse case: CORS for SPA
AuditDPoPLooseMethodCaseAdmittedDPoP proof presented htm in non-canonical case; admitted with telemetryinfoConcepts: DPoP
AuditKeyRetiredKidPresentedrequest presented a kid past the JWKS grace window — verifier rejectedwarnOperations: key rotation

Introspection

Fire from /introspect when client authentication or the RFC 7662 §2.2 wire contract rejects. The wire response stays at the canonical error; the audit event surfaces the reason for SOC tooling.

Event constantWhen it firesSeverity hintLinked doc
AuditIntrospectionError/introspect rejected the inbound client credentialswarn

Client authentication

Fire from /token and /par 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.

Event constantWhen it firesSeverity hintLinked doc
AuditClientAuthnFailure/token or /par rejected the client (wrong secret, expired assertion, alg mismatch, missing private_key_jwt)warnConcepts: client types

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
AuditDCRIATConsumedInitial Access Token redeemedinfoUse case: Dynamic Client Registration
AuditDCRIATExpiredIAT presented past TTLwarnUse case: Dynamic Client Registration
AuditDCRIATInvalidIAT signature / format invalidwarnUse case: Dynamic Client Registration
AuditDCROpenRegistrationUsedopen (no-IAT) registration acceptedinfoUse case: Dynamic Client Registration
AuditDCRClientRegisterednew client createdinfoUse case: Dynamic Client Registration
AuditDCRClientMetadataReadRAT-bearing GET against /register/{client_id}infoUse case: Dynamic Client Registration
AuditDCRClientMetadataUpdatedRAT-bearing PUT against /register/{client_id}infoUse case: Dynamic Client Registration
AuditDCRClientDeletedRAT-bearing DELETE against /register/{client_id}infoUse case: Dynamic Client Registration
AuditDCRRATInvalidRegistration Access Token rejectedwarnUse case: Dynamic Client Registration
AuditDCRMetadataValidationmetadata payload failed policywarnUse 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
AuditDeviceAuthorizationIssued/device_authorization returned a fresh device_code + user_code pairinfoConcepts: device code
AuditDeviceAuthorizationRejected/device_authorization rejected the inbound request (unknown client, scope refusal, etc.)warnConcepts: device code
AuditDeviceAuthorizationUnboundRejectedDPoP / mTLS proof was required but absent from /device_authorizationwarnConcepts: sender-constrained tokens
AuditDeviceCodeVerificationApprovedembedder's verification page reported the user approved the pending codeinfoUse case: device code
AuditDeviceCodeVerificationDeniedembedder'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
AuditDeviceCodeUserCodeBruteForcea user_code submission missed; counter incremented (lockout fires after devicecodekit.MaxUserCodeStrikes, default 5)alertUse case: device code
AuditDeviceCodeTokenIssued/token minted a token for an approved device authorizationinfoUse case: device code
AuditDeviceCodeTokenRejected/token rejected the device-code grant (access_denied, expired_token, etc.)warnUse case: device code
AuditDeviceCodeTokenSlowDown/token returned slow_down; the substore row's interval was doubledwarnUse case: device code
AuditDeviceCodeRevokedop/devicecodekit.Revoke flipped the row to denied; when Deps.AccessTokens is wired, the helper also cascade-revoked issued access tokens and reports revoked_access_tokensinfoUse case: device code

CIBA

Fire from /bc-authorize, the embedder's authentication-device interaction, and the token-endpoint CIBA grant. ciba.poll_abuse.lockout is the canonical client-misbehaviour signal.

Event constantWhen it firesSeverity hintLinked doc
AuditCIBAAuthorizationIssued/bc-authorize returned a fresh auth_req_idinfoConcepts: CIBA
AuditCIBAAuthorizationRejected/bc-authorize rejected the inbound request (unknown user, hint resolver failed, scope refusal)warnConcepts: CIBA
AuditCIBAAuthorizationUnboundRejectedDPoP / mTLS proof was required but absent from /bc-authorizewarnConcepts: sender-constrained tokens
AuditCIBAAuthDeviceApprovedthe substore observed Approve for a pending request (embedder's authentication-device callback)infoUse case: CIBA
AuditCIBAAuthDeviceDeniedthe substore observed Deny for a pending requestwarnUse case: CIBA
AuditCIBAPollAbuseLockoutpoll cadence sustained well below the negotiated interval; the request was locked outalertUse case: CIBA
AuditCIBATokenIssued/token minted a token for an approved CIBA requestinfoUse case: CIBA
AuditCIBATokenRejected/token rejected the CIBA grant (access_denied, expired_token, authorization_pending)warnUse case: CIBA
AuditCIBATokenSlowDown/token returned slow_down because the client polled faster than the negotiated intervalwarnUse case: CIBA
AuditCIBAPollObservationFailedpersisting 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 RegisterTokenExchange handler. Every successful exchange emits requested + granted; 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
AuditTokenExchangeRequested/token accepted an RFC 8693 request and entered the exchange handlerinfoConcepts: token exchange
AuditTokenExchangeGrantedexchange admitted; new access token (and optionally refresh) mintedinfoUse case: token exchange
AuditTokenExchangePolicyDeniedthe embedder-supplied TokenExchangePolicy returned a denywarnUse case: token exchange
AuditTokenExchangePolicyErrorthe policy returned a non-deny error (transient infrastructure failure)warnUse case: token exchange
AuditTokenExchangeScopeInflationBlockedrequested scope exceeded the subject_token's scope or the client's allow-listwarnUse case: token exchange
AuditTokenExchangeAudienceBlockedrequested audience was not in the policy's allow-listwarnUse case: token exchange
AuditTokenExchangeTTLCappedissued TTL was clipped against (handler request, subject_token remaining, global ceiling)infoUse case: token exchange
AuditTokenExchangeActChainTooDeepnested act chain exceeded the configured depth limitwarnUse case: token exchange
AuditTokenExchangeEmptyScopeRejectedexchange requested a non-empty scope subset that resolved to the empty setwarnUse case: token exchange
AuditTokenExchangeActorEqualsSubjectactor_token resolved to the same subject as subject_token (no delegation)infoUse case: token exchange
AuditTokenExchangeSubjectTokenExternalsubject_token did not resolve in the local registry; treated as opaque externalinfoUse case: token exchange
AuditTokenExchangeActorTokenExternalactor_token did not resolve in the local registry; treated as opaque externalinfoUse case: token exchange
AuditTokenExchangeSubjectTokenInvalidsubject_token verification failed (signature / TTL / cnf mismatch)warnUse case: token exchange
AuditTokenExchangeSubjectTokenRegistryErrorregistry lookup observed a non-NotFound fault (transient outage); wire stays invalid_grantalertUse case: token exchange
AuditTokenExchangeRefreshIssuedexchange opted into refresh issuance (IssueRefreshToken=op.PtrBool(true))infoUse case: token exchange
AuditTokenExchangeSelfExchangeexchange targeted the calling client (passive token replay scenario)warnUse case: token exchange

Custom Grant

EventMeaningLevelSee
custom_grant.requestedcustom grant_type entered the dispatcherinfoUse case: custom grant
custom_grant.failedcustom grant dispatch or handler failedwarnUse case: custom grant
custom_grant.refresh_droppedhandler 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

Event constantWhen it firesSeverity hintLinked doc
AuditGrantManagementRevokeda DELETE to the grant management endpoint revoked a grant and cascaded its tokensinfoUse case: grant management

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.

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
grep -hE 'AuditEvent\("[a-z_.]+"\)' op/audit.go \
  | grep -oE '"[a-z_.]+"' | sort -u

The output is the closed catalog this page mirrors.