The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
shinyOAuth emits structured audit events for authorization requests,
callbacks, token operations, and changes to the authenticated session.
Configure shinyOAuth.audit_hook with an R function to
receive these events as named lists and send them to your application’s
logging system.
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 your app and try signing in. You will see event names such as
audit_redirect_issued and audit_login_success.
The trace_id connects related events from the same
operation or login attempt. Several events can describe one failure;
they are not necessarily separate failed logins. For an interactive
login, the same trace_id follows redirect preparation,
callback validation, token exchange, and the login result.
To stop receiving events, set
options(shinyOAuth.audit_hook = NULL). Keep hooks fast and
avoid throwing errors. A hook that waits for a slow log service can
delay the login operation that called it.
Use audit_authenticated_changed to track the module’s
final authentication state and audit_session_cleared to
record why a session ended. Events such as
audit_token_exchange and audit_userinfo report
individual operations; subsequent validation can still fail. Callback
failures include phase and error_class fields
to help identify the failed check. The event catalog below documents the
available names and fields.
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 at the end of the
catalog. Do not assume every field exists on every event.
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.
By default, the HTTP summary omits paths, query strings, headers, and client addresses. The raw Shiny session token is also omitted. To omit HTTP context entirely:
With this setting, shiny_session[["http"]] is
NULL.
Outbound URLs retain only scheme and authority by default. To include
safe route names in audit and OTel URLs and incoming HTTP context,
configure shinyOAuth.telemetry_path_scrubber as a function
of the path returning a safe absolute path (for example
/users/:id), or NULL to omit it. Use an
allowlist or replace all identifying path segments; the function should
be idempotent. Scrubber errors and invalid results omit the path.
Scrubber input is limited to 2048 bytes; exported paths to 512 bytes.
Method, host, and scheme are limited to 32, 255, and 16 bytes. Control
and Unicode format characters are removed.
For local debugging,
shinyOAuth.audit_redact_http = FALSE includes raw request
details, and
shinyOAuth.audit_include_raw_session_token = TRUE includes
the raw session token. These can expose credentials and personal data;
keep the defaults for production logs. Redaction does not replace your
logging system’s access and retention controls.
With oauth_module_server(async = TRUE), the hook is
passed to background work too. Use a hook that can run in a separate R
process. Existing database connections or open file handles cannot be
safely carried into a worker; create connections there or use a logging
service designed for multiple writers. Appending to a global R list
inside a worker only changes that worker’s copy.
Worker failures may include mirai_error_type:
mirai_error, mirai_timeout,
mirai_connection_reset, or mirai_interrupt.
This helps distinguish an R error from a timeout, crashed worker, or
cancellation. It can be NA for a failure that is not
specific to mirai.
Digests use HMAC-SHA256 with a random process key by default. The module shares that key with its async workers. To match digests across separate app processes or restarts, configure the same secret key everywhere:
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)Use a string or raw vector containing at least 32 bytes, generated
from at least 32 cryptographically random bytes and stored as a
deployment secret. Invalid or short configured keys cause an error.
FALSE selects unkeyed SHA-256 for compatibility, but makes
low-entropy identifiers easier to guess.
For exporting events and timings through OpenTelemetry, see OpenTelemetry.
This is a lookup reference. Start with the event name in your logs; fields are included when relevant and available.
audit_callback_query_rejectedprovider, issuer,
client_id_digest, error_classphase, reason, and
handle_digest.audit_callback_routing_rejectedphase
(callback_registry_routing), reason,
status (error), and sanitized HTTP metadata in
shiny_session.http.route_unavailable,
route_unregistered, unexpected_transport,
issuer_missing, and issuer_unrecognized.shinyOAuth.callback.route span records the same
phase and reason with error status. No provider identity is assigned
from unvalidated issuer input.audit_callback_iss_missingenforce_callback_issuer = TRUE and the callback
omits the RFC 9207 iss parameterprovider, expected_issuer,
client_id_digest, error_classaudit_callback_iss_mismatchiss query parameter (per
RFC 9207) that does not match the provider’s expected issuerprovider, expected_issuer,
callback_issuer, client_id_digest,
error_classaudit_callback_iss_validation_failedprovider, expected_issuer,
callback_issuer (when present),
client_id_digest, error_classaudit_callback_receivedprovider, issuer,
client_id_digest, code_digest,
state_digest, browser_token_digestCallback validation covers both the sealed-state checks and the later checks of the values tied to that state, such as the browser token, PKCE code verifier, and nonce. Each stage emits either a success event or a failure event.
audit_callback_validation_successprovider, issuer,
client_id_digest, state_digestaudit_callback_validation_failedprovider, issuer,
client_id_digest, state_digest,
phase, error_class (+
browser_token_digest when phase is
browser_token_validation)payload_validation,
browser_token_validation,
pkce_verifier_validation, nonce_validation,
form_post_request_validation,
form_post_callback_lookup,
form_post_callback_validationhandle_digest is included when a form_post callback
handle is missing, expired, or already consumed.callback_validation_failed event.These events identify failures when reading or removing a pending login from the state store.
audit_state_store_lookup_failedstate_store fails (missing, malformed, or underlying cache
error)provider, issuer,
client_id_digest, state_digest,
error_class, phase
(state_store_lookup or
state_store_atomic_take)state_store_atomic_take phase applies when using a store
with an atomic [["take"]]() method.audit_state_store_removal_failedprovider, issuer,
client_id_digest, state_digest,
error_class, phase
(state_store_removal)Digest differences: For audit_callback_validation_failed
during payload decryption (phase = "payload_validation")
the state_digest is computed from the encrypted payload
(plaintext not yet available). For state store events the digest
reflects the plaintext state string.
audit_token_exchangeprovider, issuer,
client_id_digest, code_digest,
used_pkce, received_id_token,
received_refresh_token,
expires_in_synthesizedexpires_in_synthesized (logical): TRUE
when the token response did not include a usable expires_in
value and the package used the configured fallback token lifetimeaudit_token_exchange_errorprovider, issuer,
client_id_digest, code_digest,
error_classDetailed sender-constraint diagnostics such as DPoP token-type inference, DPoP nonce retries, and mTLS endpoint-alias selection are emitted on the OpenTelemetry spans documented in the OpenTelemetry vignette rather than on the high-level audit events.
audit_token_introspectionintrospect_token() reaches a final result (for
example during login or refresh when
introspect = TRUE)provider, issuer,
client_id_digestwhich (“access” or “refresh”)supported (logical), active (logical|NA),
statussub_digest, introspected_client_id_digest,
scope_digest (when available)status values include "ok",
"introspection_unsupported", "missing_token",
"body_too_large", "invalid_json",
"missing_active", "invalid_active", and
"http_<code>"audit_login_successOAuthTokenprovider, issuer,
client_id_digest, sub_digest,
sub_source, refresh_token_present,
expires_atsub_source indicates where sub_digest was
derived from:
userinfo: subject came from the userinfo responseid_token: subject came from an ID token that was
validated (signature + claims)id_token_unverified: subject came from an ID token
payload parse when ID token validation was not performedaudit_login_failedprovider, issuer,
client_id_digest, phase,
error_class, mirai_error_typephase currently includes:
sync_token_exchangeasync_token_exchangeasync_payload_validationasync_state_store_lookupmirai_error_type is only present on async failure
pathsaudit_logoutauth[["logout"]]() is called on the moduleprovider, issuer,
client_id_digest, reason (default
manual_logout)audit_connection_disconnectedEmitted once for each retained connection removed locally, after
bounded remote cleanup. It is emitted even when revocation is disabled
or fails. Context includes owner_digest,
connection_id_digest, retention,
reason, local_outcome,
revoke_requested, remote_refresh_outcome, and
remote_access_outcome. Reasons are disconnect,
disconnect_all, logout, or
session_end. Identifiers use the configured audit digest
policy; credentials and raw identities are excluded. Remote outcomes
describe the revocation attempt, not proof that the authorization server
previously recognized a token.
audit_connections_disconnectedSummarizes a completed local bulk removal, including an empty batch,
with owner_digest, connection_count,
retention, reason, local_outcome,
and revoke_requested. Per-connection events describe remote
outcomes. This event also covers logout and removal of session-only
connections at session end; ending a Shiny session does not remove
browser- or account-retained connections. The batch and per-connection
events share a trace ID and are available to both the native audit hook
and OpenTelemetry. Existing module logout events remain.
audit_session_clearedprovider, issuer,
client_id_digest, reason,
error_class, mirai_error_typerefresh_failed_async,
refresh_failed_sync, reauth_window,
token_expirederror_class is present on refresh failure reasons
(refresh_failed_async, refresh_failed_sync)
but absent for reauth_window and
token_expired; mirai_error_type is only
present for async refresh-failure clearsaudit_token_revocationrevoke_token() reaches a final outcome (including
early unsupported or missing_token returns)
during logout or session endprovider, issuer,
client_id_digestwhich (“access” or “refresh”)supported (logical), revoked (logical|NA),
statusstatus values include "ok",
"revocation_unsupported", "missing_token", and
"http_<code>"audit_refresh_failed_but_kept_sessionindefinite_session = TRUE in
oauth_module_server())provider, issuer,
client_id_digest, reason
(refresh_failed_async|refresh_failed_sync),
kept_token (TRUE), error_class,
mirai_error_typemirai_error_type is only present on async refresh
failuresaudit_invalid_browser_tokenshinyOAuth_sid
value from the browser and requests regenerationprovider, issuer,
client_id_digest, reason,
lengthaudit_token_refreshrefresh_token() successfully refreshes the access
tokenprovider, issuer,
client_id_digest, refresh_token_rotated,
new_expires_at, expires_in_synthesizedexpires_in_synthesized (logical): TRUE
when the refresh response did not include a usable
expires_in value and the package used the configured
fallback token lifetimeaudit_userinfoget_userinfo() is called to retrieve user
information (emitted on success and various failure modes)provider, issuer,
client_id_digest, sub_digest,
statusstatus values:
"ok" – userinfo successfully parsed"parse_error" – response could not be parsed as JSON or
JWT. Additional fields: http_status, url,
content_type, body_digest"userinfo_missing_sub" – OIDC userinfo response was
parsed but omitted the required sub claim"userinfo_not_jwt" – signed JWT required but response
was not application/jwt. Additional fields:
content_type"userinfo_jwt_encrypted" – userinfo response was a JWE,
which ‘shinyOAuth’ does not decrypt"userinfo_jwt_header_parse_failed" – JWT header could
not be parsed"userinfo_jwt_header_invalid" – JWT header parsed but
was malformed or structurally invalid"userinfo_jwt_typ_invalid" – JWT header
typ did not indicate a JWT"userinfo_jwt_unsigned" – JWT uses
alg=none. Additional fields: jwt_alg"userinfo_jwt_alg_rejected" – JWT algorithm not in
provider’s allowed asymmetric algorithms. Additional fields:
jwt_alg"userinfo_jwt_no_issuer" – provider issuer not
configured for JWKS verification"userinfo_jwt_jwks_fetch_failed" – JWKS fetch failed
during signature verification"userinfo_jwt_signature_invalid" – signature
verification failed against candidate JWKS keys"userinfo_jwt_no_matching_key" – provider JWKS had no
compatible key for the JWT"userinfo_jwt_payload_parse_failed" – JWT payload could
not be parsed as JSON"userinfo_jwt_missing_sub",
"userinfo_jwt_missing_iss",
"userinfo_jwt_missing_aud" – signed JWT omitted a required
claim"userinfo_jwt_iss_mismatch",
"userinfo_jwt_aud_mismatch" – signed JWT claims did not
match the configured issuer/client"userinfo_jwt_missing_required_temporal_claims" –
signed JWT omitted required temporal claims such as exp or
iat"userinfo_jwt_invalid_exp",
"userinfo_jwt_invalid_iat",
"userinfo_jwt_invalid_nbf" – temporal claim was present but
not a single usable numeric value"userinfo_jwt_expired",
"userinfo_jwt_iat_future",
"userinfo_jwt_nbf_future" – temporal claim failed time
validationState parsing failures occur while decoding and validating the encrypted wrapper prior to extracting the logical state value, and also when deriving a cache key from a malformed logical state string.
audit_state_parse_failurephase (decrypt or
cache_key), a reason code, and either
token_digest (phase = decrypt) or
state_digest (phase = cache_key), plus any
additional details (such as lengths). Emitted best-effort from parsing
utilities and never interferes with control flow.Provider error callbacks still need valid login state and browser
binding. The events below report the one-time state consumption, not the
outcome of all callback checks. Browser mismatches use
audit_callback_validation_failed with
phase = "browser_token_validation".
audit_error_state_consumedprovider, issuer,
client_id_digest, state_digestaudit_error_state_consumption_failedprovider, issuer,
client_id_digest, state_digest,
error_class, error_messageDigest note: when the callback state can be decrypted,
these events use the logical plaintext state digest so they correlate
with audit_redirect_issued and the normal callback
validation/store events. If decryption fails, the digest falls back to
the encrypted callback payload because the logical state is unknown.
audit_session_startedoauth_module_server())
is initialized for a Shiny sessionmodule_id, ns_prefix,
client_provider, client_issuer,
client_id_digest, plus the standard
shiny_session context described aboveaudit_session_endedonSessionEnded, regardless of configuration)provider, issuer,
client_id_digest, was_authenticatedaudit_session_ended_revokerevoke_on_session_end = TRUE and a token was presentprovider, issuer,
client_id_digest; the actual revocation attempt is logged
separately as audit_token_revocation eventsaudit_authenticated_changed[["authenticated"]] reactive value changes
(TRUE ↔︎ FALSE)provider, issuer,
client_id_digest, authenticated,
previous_authenticated, reasonlogin (when becoming authenticated),
or the error code/state that caused de-authentication (e.g.,
token_expired, logged_out,
token_cleared)In addition to the audit_* events above, the hook also
receives error events emitted just before the package raises an R error
condition. These let you log failures to the same sink as audit
events.
errortype ("error"), trace_id,
message,context fields from the call site (typically
provider, issuer,
client_id_digest, phase,
error_class)http_errortype ("http_error"),
trace_id, messagestatus: HTTP status code (integer, or NA
if unavailable)url: the request scheme and authority, plus an approved
path only when a path scrubber is configuredbody_digest: HMAC-SHA-256 hex digest of the response
body using the configured or per-process audit digest key (for
correlation without leaking content)oauth_error, oauth_error_uri: RFC 6749
§5.2 structured error fields extracted from JSON error responses (e.g.,
from the token endpoint)oauth_error_description: included only when
options(shinyOAuth.expose_error_body = TRUE) is enabled for
debugging, because provider-controlled text can contain request-specific
detailscontext fields from the call sitetransport_errortype ("transport_error"),
trace_id, messagecontext fields from the call siteThese binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.