An audit log records login attempts, token operations and session changes, helping explain a failed sign-in or why a session ended.
shinyOAuth sends each event to an R function called a hook. Your hook decides where to write it: the console for local testing, or your application’s logging service in a deployment. For timings and monitoring dashboards, see OpenTelemetry.
Put this near the top of app.R, before creating the
provider and client:
options(shinyOAuth.audit_hook = function(event) {
cat(sprintf("[shinyOAuth] %s | %s\n", event[["type"]], event[["trace_id"]]))
str(event)
})Run the app and sign in. Events such as
audit_redirect_issued and audit_login_success
describe the steps taken. The trace_id connects events from
the same login attempt, including failures spanning several steps.
Several error events can describe one failed login.
The hook runs as part of the recorded operation, so a slow logging
service can delay login.
options(shinyOAuth.audit_hook = NULL) disables the
hook.
Start with audit_authenticated_changed for the module’s
authentication state and audit_session_cleared for why a
session ended. A successful token exchange is an intermediate step;
later identity checks can still reject the login.
Audit events have type, trace_id, and a
timestamp from Sys.time(), plus fields
specific to the operation. The hook also receives error events such as
error, http_error, and
transport_error; their fields are listed in the catalog.
Fields depend on the event and available context.
When a Shiny session is available, shiny_session
contains:
session_token_digest: a protected identifier for
correlating the session.is_async and process_id: whether work ran
in a background worker and which R process emitted the event. Async
context also includes main_process_id.http: a summary with request method, host, and scheme
by default.The session context is a JSON-friendly list suitable for
jsonlite::toJSON(); the raw Shiny
session[["request"]] object is not included. If
shinyOAuth.audit_include_raw_session_token = TRUE, the raw
session token is available as shiny_session[["token"]] in
native hook events.
Fields ending in _digest allow matching without
recording the original token or identifier. The package’s
trace_id is separate from OpenTelemetry trace/span IDs;
OTel exports it as shinyoauth.trace_id.
The default HTTP summary omits raw session tokens, request paths, query strings, headers and client addresses.
To omit HTTP context entirely:
shinyOAuth.telemetry_path_scrubber supplies route labels
for logs, for example replacing /users/123 with
/users/:id. Paths remain omitted when no scrubber is
configured.
With async = TRUE, the hook also runs in background R
processes. Shared logging needs a service supporting several writers and
connections opened within each process. A global list collects only the
current process’s events.
Digest fields let you match events without logging the original identifier. The default key lasts for one R process and its async workers. If you need to match records across app processes or restarts, load the same secret key in each:
audit_digest_key <- Sys.getenv("AUDIT_DIGEST_KEY", unset = NA_character_)
if (is.na(audit_digest_key) || !nzchar(audit_digest_key)) {
stop("AUDIT_DIGEST_KEY must be configured before the app starts")
}
options(shinyOAuth.audit_digest_key = audit_digest_key)The shared key contains at least 32 cryptographically random bytes and provides log correlation across processes, independently of login-state keys.
Event names identify the operation below. trace_id links
records from one attempt; phase, reason,
status and error_class add context when
available. audit_login_success records completion of the
login checks.
| Name | What it tells you |
|---|---|
audit_redirect_issued |
The authorization URL is ready. |
audit_callback_received |
Encrypted-state validation passed; later checks can still fail. |
audit_callback_query_rejected |
Callback parameters are malformed or too large. |
audit_callback_routing_rejected |
The callback could not be assigned to a configured client. |
audit_callback_iss_missing |
The callback omitted a required provider identifier. |
audit_callback_iss_mismatch |
The provider identifier does not match the expected issuer. |
audit_callback_iss_validation_failed |
Another issuer check failed. |
audit_callback_validation_success |
State, browser binding and single-use checks passed. |
audit_callback_validation_failed |
A callback check failed; inspect phase and
error_class. |
audit_state_parse_failure |
Login state could not be decoded or validated. |
audit_state_store_lookup_failed |
The pending login record could not be read. |
audit_state_store_removal_failed |
The pending record could not be consumed safely. |
audit_error_state_consumed |
State from a provider error response was consumed; other checks can still fail. |
audit_error_state_consumption_failed |
State from a provider error response could not be consumed. |
audit_browser_cookie_error |
Browser cookie setup failed or Web Crypto is unavailable. |
audit_invalid_browser_token |
The browser sent an invalid binding token; the module requests a replacement. |
audit_login_success |
Callback handling completed its configured checks. |
audit_login_failed |
Login failed during callback handling. |
| Name | What it tells you |
|---|---|
audit_token_exchange |
An authorization code was exchanged for tokens. |
audit_token_exchange_error |
Token exchange failed. |
audit_token_refresh |
The access token was refreshed. |
audit_token_introspection |
A token-activity check finished; inspect active and
status. |
audit_token_revocation |
A revocation attempt finished, or was unsupported or missing a token. |
audit_userinfo |
Profile retrieval or validation finished; status
describes the result. |
| Name | What it tells you |
|---|---|
audit_session_started |
The authentication module started for a Shiny session. |
audit_authenticated_changed |
The module authentication state changed. |
audit_logout |
The module received a logout request. |
audit_session_cleared |
Local login state was cleared; reason explains
why. |
audit_refresh_failed_but_kept_session |
Refresh failed, but indefinite_session = TRUE retained
local login state. |
audit_connection_disconnected |
One retained connection was removed locally. |
audit_connections_disconnected |
A bulk local removal finished, including an empty batch. |
audit_session_ended |
The Shiny session ended. |
audit_session_ended_revoke |
Session end triggered configured token-revocation cleanup. |
| Name | What it tells you |
|---|---|
error |
The package raised an R error; inspect the error class and available context. |
http_error |
A provider returned an unsuccessful HTTP status. |
transport_error |
A request failed before a response arrived, for example after a timeout. |
Disconnect events confirm local removal even if remote revocation
fails. The per-connection remote_refresh_outcome and
remote_access_outcome describe the remote attempts; an
accepted request does not prove the provider recognized the token.
Ending a Shiny session leaves browser- and account-retained connections
in place.
For audit_userinfo, status distinguishes
successful parsing from invalid JSON, missing identity fields, and
signature or claim failures. HTTP error records can also include
status and structured oauth_error fields.
Free-form provider messages are subject to the package’s diagnostic
redaction policy.
For DPoP nonce handling and mTLS endpoint selection, inspect the related OpenTelemetry spans.