--- title: "Audit logging and hooks" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Audit logging and hooks} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ## Overview 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](opentelemetry.html). ## Receiving audit events Put this near the top of `app.R`, before creating the provider and client: ```{r, eval = FALSE} 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. ## Authentication outcomes 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. ## Event structure 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`. ## HTTP context and redaction The default HTTP summary omits raw session tokens, request paths, query strings, headers and client addresses. To omit HTTP context entirely: ```{r, eval = FALSE} options(shinyOAuth.audit_include_http = FALSE) ``` `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. ## Audit events from asynchronous workers 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 keys across R processes 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: ```{r, eval = FALSE} 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 catalog 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. ### Login and callbacks | 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. | ### Tokens and profiles | 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. | ### Sessions and saved connections | 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. | ### Error records | 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](opentelemetry.html#span-catalog).