Releases: lukakoning/shinyOAuth
shinyOAuth 0.4.0
-
'mirai' & async backend improvements:
- Warnings and messages emitted in async workers (e.g., missing
expires_in
from token response) are now captured and re-emitted on the main process so
they appear in the R console. This includes conditions from user-supplied
trace_hook/audit_hookfunctions: warnings, messages, and errors
(surfaced as warnings) all propagate back to the main thread. Replay can be
disabled viaoptions(shinyOAuth.replay_async_conditions = FALSE). - Async callback flow no longer serializes the full client object (including
potentially non-serializable customstate_store/ JWKS cache backends)
into the worker context. Thestate_store(already consumed on the main
thread) is replaced with a lightweight serializable dummy before dispatch.
If the client still fails serialization, the flow falls back to synchronous
execution with an explicit warning instead of an opaque runtime error. - Further reduced serialization overhead towards async workers by using
certain functions from the package namespace directly. - Detect active daemons via
mirai::daemons_set()instead of
mirai::status(). Falls back tomirai::info()on older 'mirai' versions
that lackmirai::daemons_set()(< 2.3.0). - Configurable per-task timeout via
options(shinyOAuth.async_timeout)
(milliseconds); timed-out 'mirai' tasks are automatically cancelled by the
dispatcher. Default isNULL(no timeout). - Async audit events now include a
mirai_error_typefield. This classifies
mirai transport-level failures separately from application-level errors. - Prevent 'mirai' warning spam about 'stats' maybe not being available in
workers.
- Warnings and messages emitted in async workers (e.g., missing
-
ID token validation (
validate_id_token()):- Now enforces RFC 7515 section 4.1.11 critical header
parameter (crit) processing rules. Tokens containing unsupported critical
extensions are rejected with ashinyOAuth_id_token_error. The current
implementation supports no critical extensions, so anycritpresence
triggers rejection. - Now validates the
at_hash(Access Token hash) claim
when present in the ID token (per OIDC Core section 3.1.3.8 and 3.2.2.9). If
the claim exists, the access token binding is verified; a mismatch raises a
shinyOAuth_id_token_error. Newid_token_at_hash_requiredproperty on
OAuthProvider(defaultFALSE) forces login to fail when the ID token does
not contain anat_hashclaim. - Now validates, for refreshed ID tokens, per OIDC Core section 12.2,
iss
andaudclaims against the original ID token's values (not just the provider
configuration) to cover edge cases with multi-tenant providers or rotating
issuer URIs. Enforced in both validated and non-validated code paths. - Now detects encrypted ID tokens (JWE compact serialization, 5 dot-separated
segments) early and raises a clearshinyOAuth_id_token_errorinstead of
letting a confusing alg/typ/parse failure propagate. - Now validates the
auth_timeclaim whenmax_ageis
present inextra_auth_params(OIDC Core section 3.1.2.1). - Now enforces a maximum ID token lifetime (
exp - iat) per OIDC Core
section 3.1.3.7; tokens with unreasonably long lifetimes are rejected with a
shinyOAuth_id_token_error. Configure via
options(shinyOAuth.max_id_token_lifetime = <seconds>)(default of86400
which is 24 hours). Set toInfto disable the check.
- Now enforces RFC 7515 section 4.1.11 critical header
-
Stricter state store usage:
custom_cache()gains an optionaltakeparameter for atomic get-and-delete.state_store_get_remove()prefers$take()when available; falls back to
$get()+$remove()with a mandatory post-removal absence check (instead
of trusting$remove()return values).- Non-
cachem::cache_mem()stores without$take()now error by default
to prevent TOCTOU replay attacks in shared/multi-worker deployments.
To bypass this error, operators must explicitly acknowledge the risk by
settingoptions(shinyOAuth.allow_non_atomic_state_store = TRUE), which
downgrades the error to a warning. OAuthClientvalidator now validates$take()signature when present.- The
$remove()return value is no longer relied upon in the fallback path;
the post-removal$get()absence check is authoritative.
-
Stricter JWKS cache handling: JWKS cache key now includes host-policy fields
(jwks_host_issuer_match,jwks_host_allow_only). Previously, two provider
configs for the same issuer with different host policies shared the same cache
entry, allowing a relaxed-policy provider to populate the cache and a
strict-policy provider to skip host validation on cache hit. Cache entries now
also store the JWKS source host and re-validate it against the current
provider policy on read (defense-in-depth). -
Stricter URL validation:
OAuthClientnow rejects redirect URIs containing
fragments (per RFC 6749, section 3.1.2);OAuthProvidernow rejects issuer
identifiers containing query or fragment components, covering both
oauth_provider_oidc_discover()and manual construction of providers. -
Stricter state payload parsing: callback
statenow rejects embedded NUL
bytes before JSON decoding. -
Stricter response size validation: enforce max response body size on all
outbound HTTP endpoints (token, introspection, userinfo, OIDC discovery, JWKS).
Curl aborts the transfer early whenContent-Lengthexceeds the limit; a
post-download guard catches chunked responses. Default 1 MiB, configurable via
options(shinyOAuth.max_body_bytes). -
OAuthProvider(S7 class):leewayvalidator now rejects non-finite values (Inf,
-Inf,NaN). Previously these passed validation but were silently coerced
to 0 at runtime, effectively disabling clock-skew tolerance.- Reserved OAuth parameter blocking in
extra_auth_paramsand
extra_token_paramsis now case-insensitive and trims whitespace. - Vector inputs for
pkce_methodand URL parameters (auth_url,token_url,
userinfo_url,introspection_url,revocation_url) now produce clear
scalar-input errors instead of cryptic coercion failures.
-
OAuthClient(S7 class):- Gains a
claims_validationproperty; when the client sends a structured
claimsrequest parameter withessential = TRUEentries, this setting
controls whether the returned ID token and/or userinfo response are checked
for those essential claims (similar toscope_validation). - Gains a
required_acr_valuesproperty; enables client-side
enforcement of the OIDCacr(Authentication Context Class Reference) claim. extra_token_headersare now consistently applied to revoke
and introspect requests, matching the existing behavior for token exchange and
refresh. Previously, provider integrations requiring custom headers across all
token endpoints could partially fail on revocation/introspection.- Fixed incorrect warning about client being created in Shiny when this was
not the case. - Malformed
client_assertion_algandclient_assertion_audiencevalues
(e.g.,character(0), multi-element vectors) now produce clear validation
errors instead of crashing with base R subscript-out-of-bounds errors. Empty
string""forclient_assertion_audienceis now explicitly rejected instead
of being silently treated as "not provided".
- Gains a
-
OAuthToken(S7 class):- Gains a read-only
id_token_claimsproperty that exposes the
decoded ID token JWT payload as a named list, surfacing all OIDC claims
(e.g.,acr,amr,auth_time) without manual decoding. - Gains an
id_token_validatedproperty (logical) indicating whether the ID
token was cryptographically verified during the OAuth flow.
- Gains a read-only
-
oauth_module_server():- Now surfaces
error_urifrom provider error
callbacks (RFC 6749, section 4.1.2.1). The new$error_urireactive field
contains the URI to a human-readable error page when the provider includes
one;NULLotherwise. Theerror_uricallback parameter is also validated
against a configurable size limit
(e.g.,options(shinyOAuth.callback_max_error_uri_bytes = 2048)). - OAuth callback query cleanup is now also applied in early
return paths of internal function.process_query(), ensuring more consistent
cleanup.
- OAuth callback query size caps are now enforced even when the user is
already authenticated. Previously, the "token already present" branch in
.process_query()called.query_has_oauth_callback_keys()(which parses
the query string) before any size validation, bypassing the intended DoS
guardrails. Thevalidate_untrusted_query_string()check now runs
unconditionally at the top of.process_query().
- OAuth callback error responses (
?error=...) now
require a validstateparameter. Missing/invalid/consumed state is then
treated properly as aninvalid_stateerror instead of surfacing the error
from?error=...(which could be set by an attacker). - OAuth callback including an
issquery parameter now validate this against
the provider's configured/discovered issuer during callback processing
(complementing the existing ID tokenissclaim validation that occurs
post-exchange) (per RFC 9207). A mismatch produces anissuer_mismatcherror
and audit event, defending against authorization-server mix-up attacks in
multi-provider scenarios. Whenissis absent, current behavior is retained
(no enforcement).
- Now surfaces
-
handle_callback(): no longer acceptsdecrypted_payloadand
state_store_valuesbypass parameters. These parameters were only intended for
internal use byoauth_module_server()'s async path. As they can be misused by
direct/custom callers to bypass important security checks, they have been
moved to an internal-only helper function (handle_callback_internal()). -
handle_callback()/refresh_token(): when a token response omits
expires_in, a warning is now emitted once per...
shinyOAuth 0.3.0
-
Async backend: the default async backend is now 'mirai' (>= 2.0.0) for
simpler and more efficient asynchronous execution. Usemirai::daemons()to
configure async workers. A 'future' backend configured withfuture::plan()
is still supported, but 'mirai' takes precedence if both are configured. -
Test suite: fixed inconsistent results of several tests; tests not suitable
for CRAN now skip on CRAN. Silenced test output messages to avoid confusion.
shinyOAuth 0.2.0
New/improved
Security
-
Token revocation: tokens can now be revoked when Shiny session ends. Enable
viarevoke_on_session_end = TRUEinoauth_module_server(). The provider must
expose arevocation_url(auto-discovered for OIDC, or set manually via
oauth_provider()). New exported functionrevoke_token(). -
Token introspection on login: validate tokens via the provider's introspection
endpoint during login. Configure viaintrospectandintrospect_elements
properties onOAuthClient. The provider must expose anintrospection_url
(auto-discovered for OIDC, or set manually viaoauth_provider()). -
DoS protection: callback query parameters and state payload/browser token
sizes are validated before expensive operations (e.g., hashing for audit logs).
Maximum size may be configured viaoptions(); see section 'Size caps' in
vignette("usage", package = "shinyOAuth"). -
DoS protection: rate-limited JWKS refresh: forced JWKS cache refreshes (triggered by unknown
kid) are now rate-limited to prevent abuse. -
JWKS pinning: pinning is now enforced during signature verification: previously,
jwks_pinswithjwks_pin_mode = "any"only verified that at least one key
in the JWKS matched a pin, but signature verification could still use any
matching key (pinned or not). Now, signature verification is restricted to
only use keys whose thumbprints appear in the pin list, ensuring true key
pinning rather than presence-only checks. -
use_shinyOAuth()now injects<meta name="referrer" content="no-referrer">
by default to reduce leaking ?code=...&state=... via the Referer header on the
callback page. Can be disabled with
use_shinyOAuth(inject_referrer_meta = FALSE). -
Sensitive outbound HTTP requests (token exchange/refresh, introspection,
revocation, userinfo, OIDC discovery, JWKS) now by default disable redirect
following and reject 3xx responses to prevent bypassing host/HTTPS policies.
Configurable viaoptions(shinyOAuth.allow_redirect = TRUE).client_bearer_req()
also gainsfollow_redirect, which defaults toFALSE, to similarly control redirect
behavior for requests using bearer tokens. -
State is now also consumed in login failure paths (when the provider
returns an error but also a state). -
Callback URL parameters are now also cleared in login failure paths.
-
OAuthProvidernow requires absolute URLs (scheme + hostname) for all
endpoint URLs. -
Provider fingerprint now includes
userinfo_urlandintrospection_url,
reducing risk of misconfiguration when multiple providers share endpoints. -
state_payload_max_ageproperty onOAuthClientfor independent freshness validation
of the state payload'sissued_attimestamp. -
Default client assertion JWT TTL reduced from 5 minutes to 120 seconds,
reducing the window for replay attacks while allowing for clock skew.
Auditing
-
New audit events:
session_ended(logged on Shiny session close),
authenticated_changed(logged when authentication status changes),
token_introspection(whenintrospect_token()is used),token_revocation
(whenrevoke_token()is used),error_state_consumedand
error_state_consumption_failed(called when provider returns an error during
callback handling and the state is attempted to be consumed). -
All audit events now include
$process_id,$is_async, and$main_process_id
(if called from an async worker); these fields help identify which process
generated the event and whether it was from an async worker. Async
workers now also properly propagate audit hooks from the main process (see 'Fixed'). -
Audit event
login_successnow includessub_sourceto indicate whether the
subject digest came fromuserinfo,id_token(verified), orid_token_unverified. -
Audit digest keying: audit/event digests (e.g.,
sub_digest,browser_token_digest)
now default to HMAC-SHA256 with an auto-generated per-process key to reduce
reidentification/correlation risk if logs leak. Configure a key with
options(shinyOAuth.audit_digest_key = "..."), or disable keying (legacy deterministic
SHA-256) withoptions(shinyOAuth.audit_digest_key = FALSE). -
HTTP log sanitization: sensitive data in HTTP contexts (headers, cookies) is
now sanitized by default in audit logs. Can be disabled with
options(shinyOAuth.audit_redact_http = FALSE). Use
options(shinyOAuth.audit_include_http = FALSE)to not include any HTTP data in
logs.
UX
-
Configurable scope validation:
validate_scopesproperty onOAuthClient
controls whether returned scopes are validated against requested scopes
("strict","warn", or"none"). Scopes are now normalized (alphabetically
sorted) before comparison. -
OAuthProvider: extra parameters are now blocked from overriding reserved keys
essential for the OAuth 2.0/OIDC flow. Reserved keys may be explicitly overridden via
options(shinyOAuth.unblock_auth_params = c(...), shinyOAuth.unblock_token_params = c(...), shinyOAuth.unblock_token_headers = c(...)). It is also validated early that
all parameters are named, catching configuration errors sooner. -
Added warning about negative
expires_invalues in token responses. -
Added warning when
OAuthClientis instantiated inside a Shiny session; may
cause sealed state payload decryption to fail when random secret is generated
upon client creation. -
Added hints in error messages when sealed state payload decryption fails.
-
Ensured a clearer error message when token response is in unexpected format.
-
Ensured a clearer error when retrieved state store entry is in unexpected format.
-
Ensured a clearer error message when retrieved userinfo cannot be parsed as JSON.
-
Immediate error when
OAuthProviderusesHS*algorithm but
options(shinyOAuth.allow_hs = TRUE)is not enabled; also immediate error whenOAuthProvider
usesHS*algorithm and ID token verification can happen butclient_secretis
absent or too weak. -
build_auth_url()now uses package-typed errors (err_invalid_state())
instead of genericstopifnot()assertions, ensuring consistent error
handling and audit logging.
Other
-
ID token signature/claims validation now occurs before fetching
userinfo. This ensures cryptographic validation passes before making external
calls to the userinfo endpoint. -
When fetching JWKS, if
key_opsis present on keys, only keys withkey_ops
including"verify"are considered. -
oauth_provider()now defaultsallowed_token_typestoc("Bearer")for all
providers. This prevents accidentally misusing non-Bearer tokens (e.g., DPoP,
MAC) as Bearer tokens. Setallowed_token_types = character()to opt out.
Token type is also now validated before calling the userinfo endpoint. -
client_assertion_audienceproperty onOAuthClientallows overriding the
JWT audience claim for client assertion authentication.
Fixed
-
Package now correctly requires
httr2>= 1.1.0. -
authenticatednow flips toFALSEpromptly when a token expires or
reauth_after_secondselapses, even without other reactive changes. Previously,
the value could remainTRUEpast expiry until an unrelated reactive update
triggered re-evaluation. -
HTTP error responses (4xx/5xx) are now correctly returned to the caller
immediately instead of being misclassified as transport errors and retried. -
Async worker options propagation: all R options are now automatically
propagated to async workers when usingasync = TRUE. Previously, options set
in the main process (includingaudit_hook,trace_hook, HTTP settings, and
any custom options) were not available infuture::multisessionworkers. -
oauth_provider_microsoft(): fixed incorrect default which blocked
multi-tenant configuration. -
oauth_provider_oidc_discover(): stricter host matching;?and*
wildcards now correctly handled. -
Fixed potential auto-redirect loop after authentication error has surfaced.
-
Fixed potential race condition between proactive refresh and expiry watcher:
the expiry watcher now defers clearing the token and triggering reauthentication
while a refresh is in progress. -
Token expiry handling during token refresh now aligns with how it is handled
during login. -
State payload
issued_atvalidation now applies clock drift leeway (from
OAuthProvider@leeway/shinyOAuth.leewayoption), consistent with ID token
iatcheck.
shinyOAuth 0.1.4
-
Added a console warning about needing to access Shiny apps with
oauth_module_server()in a regular browser; also updated examples and vignettes
to further clarify this. -
oauth_module_server(): improved formatting style of warning messages
(now consistent with error messages).
shinyOAuth 0.1.3
-
Rewrote
vignette("authentication-flow")to improve clarity. -
Skip timing-sensitive tests on CRAN.
shinyOAuth 0.1.1
- Initial CRAN submission.