Shared HMAC secrets are bound to one algorithm across enabled ID token, JARM, JAR and JWT endpoint authentication profiles, including inferred defaults and metadata-selected algorithms. Construction and use reject conflicting choices; endpoint overrides using distinct secrets can still select different algorithms.
OAuth HTTP error parsing preserves JSON types and rejects duplicate members.
Malformed error codes cannot mark a refresh credential as safe to reuse;
uncertain requests retain their conservative possibly_consumed outcome.
Verified authorization error callbacks clear their completed browser binding. Repeated denied logins no longer accumulate marker cookies and exhaust HTTP header limits; bindings for other pending transactions remain independent.
Connection polling reuses redacted lifecycle metadata for unchanged store revisions across sessions, instead of decrypting every grant on each tick. Expiry, owner state, client policy, credential retirement and changed revisions are still checked. Explicit enumeration prunes shared indexes once per batch and validates each client's current policy once; use-time reads remain strict.
Resource-request documentation and examples use perform_resource_req() for
bounded encoded and decoded response handling, including prepared requests.
They now state that independently executing resource_req() with httr2 does
not provide shinyOAuth's decoded-size guarantee on older libcurl versions.
JSON duplicate-member scanning uses byte offsets for UTF-8 input. Many short keys following non-ASCII text no longer trigger repeated Unicode prefix scans; escaped-key equivalence, surrogate validation and nesting limits are preserved.
Form callback parsing uses preallocated storage and a hashed duplicate check. Many distinct fields no longer cause quadratic allocation and scanning, and callbacks decode each field once while retaining strict percent and duplicate validation.
Malformed explicit JWKS host pins now fail closed during provider construction, S7 assignment, discovery and runtime host checks. Vectors and invalid types no longer silently remove the restriction; NULL and scalar NA still omit it.
OIDC discovery rejects unsupported-only token authentication advertisements instead of selecting unadvertised Basic authentication. Omitted metadata still defaults to Basic; supported methods can coexist with extension methods.
Resource request helpers reject malformed HTTP methods before attaching credentials, including inherited request methods. Whitespace and control characters can no longer bypass method restrictions or alter request framing.
Async worker spans record their final status, error events and token-operation outcomes before restoring worker-local telemetry options and exporter settings. Reused workers with tracing previously disabled retain complete outcomes while still restoring their prior settings after successful or failed work.
OAuth 2.1 assessment ruleset 1.1.1 reports file-backed mTLS credential
usability as unresolved instead of passing based on configured paths. This
includes endpoint overrides and certificate-bound UserInfo. Assessment keeps
its no-key-loading behavior; runtime still validates each current pair.
DPoP nonce and retry unit tests inspect prepared proofs at their mocked transport boundaries without starting local HTTP echo servers. Repeated visits verify that each endpoint retains its own nonce state; separate real-HTTP tests continue to cover transmitted proofs.
SMART scope comparisons index resource permissions and exact search constraints within each comparison, avoiding repeated scans of every granted scope during connection polling. Changed grants, policies, and credential material continue to be revalidated on each operation.
Self-signed mTLS registration exports Ed25519 certificates as public OKP JWKs with their certificate chain, using the same public-key exporter as other signing-key metadata. Private key material remains excluded.
HMAC ID-token validation binds each client secret to exactly one HS algorithm, rejecting multiple-HS configurations during client validation and runtime verification, including configuration restored without S7 property setters.
OIDC discovery applies the client_secret_basic default before validating an
explicit token authentication choice. Omitted metadata no longer permits
unadvertised public, POST, JWT, or mTLS methods; empty method arrays are rejected.
HTTP requests performed by shinyOAuth suppress httr2's automatic full-URL spans, preventing private paths and query strings from bypassing telemetry redaction or the package tracing switch. Other application HTTP requests retain their own instrumentation.
The discovery playground consistently uses Google OIDC, pins Google's signing
key host, and reads the token's userinfo property. Its unused shinyjs
dependency has been removed.
The independent JAR/PAR conformance fixture rejects authorization requests with missing, empty or mismatched outer client identifiers, with negative checks for both GET and POST authorization.
Async callbacks fall back to synchronous execution before code redemption when
custom JWKS caches have not declared worker safety. custom_cache() gains an
explicit worker_safe opt-in for backends that reconnect resources inside each
operation; serialization success alone no longer qualifies these caches.
Exact numeric claim constraints distinguish adjacent large integers and retain binary64 precision in ordinary claims requests and signed JWTs. Unsupported non-finite or out-of-safe-range numeric constraints are rejected; larger exact identifiers can be requested as strings.
Browser-token input is validated in its original form. Vectors and lists no longer adopt a valid first element, and malformed input produces only a scalar length diagnostic before requesting token repair.
Connection-manager HTTP entry rejects oversized and malformed queries before SMART launch classification, preventing unbounded parsing and allocation on ordinary application routes as well as launch and callback routes.
Provider and discovery validation reject issuer identifiers with empty query
components (?) before making discovery requests. Encoded path delimiters
such as %3F remain valid.
Internal OpenTelemetry active-span, attribute and propagation calls apply the diagnostic exposure policy, including SDK setup messages during nested OAuth operations and context capture.
PAR expiry deadlines start conservatively at the successful request attempt, including a DPoP nonce retry. Responses already expired on delivery are rejected before a browser redirect can be returned.
OAuth 2.1 assessments select UserInfo endpoints using resource-token binding policy rather than token-endpoint client authentication. Unresolved binding-dependent HTTPS choices are reported as unknown.
SMART replacement authorization, explicit refresh narrowing and transaction contexts support the profile's 256-scope / 64 KiB comparison budget throughout the lifecycle. Overlapping request and broader grant evidence is compared semantically. Configurable callback and state-envelope limits still apply.
SMART replacement authorization keeps configured or narrowed request limits when servers grant broader wildcard scopes. Actual grants remain separate evidence for replacement acceptance and prior-grant refresh checks.
Protocol JSON is parsed as literal text throughout, preventing malformed responses or state envelopes from causing implicit URL fetches or file reads. The minimum supported jsonlite version is now 1.6.
Callback bridges limit each transaction to two pending candidates in a bounded
pool and reject overflow instead of evicting an unexpired response. Shared
callback stores now require atomic set_if_absent as well as take to
preserve pending candidates when workers race.
check_oauth21() compares canonical assertion algorithm names so supported
EdDSA and Ed25519 credentials agree with runtime signing at token, PAR,
introspection, and revocation endpoints.
DPoP and mTLS confirmation checks preserve access-token JSON types and reject arrays in place of confirmation objects or scalar thumbprints. Token responses and introspection use the same confirmation-shape validation.
Manual UserInfo retrieval applies subject-binding and signed-response policies
independently of automatic retrieval. Both provider constructors require a
validated ID token by default for an OIDC UserInfo endpoint, including when
userinfo_required = FALSE.
JARM algorithm mismatch errors withhold received header values by default, including in HTTP callback responses. Opt-in diagnostics sanitize and bound values for signing, key encryption and content encryption mismatches.
Token verification rejects supplied ID tokens with malformed scalar types or empty values, including refresh responses. Optional omitted ID tokens retain the previously validated identity during refresh.
The Spotify vignette checks current operation scopes and authorization IDs, clears cached listening data after permission loss or login replacement, and uses a connection request to enforce API destinations and refresh credentials.
Multi-issuer clients recognize JARM response modes inherited from provider defaults. Constructors, class validation and authorization requests share the same response-mode policy, including conflicts and advertised support.
ID tokens reject present non-string nonce claims even when nonce use is
disabled, including explicit JSON null. Requested nonces still require an
exact match to the authorization transaction.
Wire scopes and OAuth error text reject final line breaks instead of accepting them at a permissive regular-expression boundary. Malformed scope responses cannot become verified grants through parsing, refresh or introspection.
SMART clients with OIDC identity reject finite module reauthentication ages at startup because remote freshness overrides are unsupported, avoiding a failed login after the age limit expires. OAuth-only SMART lifetimes remain available.
Malformed UTF-8 state envelopes and protocol JSON are rejected before string scanning, preventing parser warnings during callback-store inspection.
check_oauth21() documents the warn check status and its effect on the
configuration verdict, including compatibility with missing token types.
Numeric OIDC claims requirements retain their precision in ordinary authorization parameters and signed Request Objects for every supported signing algorithm. Strict claim validation now checks the values that were actually requested without authorization-time rounding.
Ed25519 client assertions and signed authorization requests now work through the public authorization flow. Their public signing key is fingerprinted using the same OKP-capable export as DPoP, retaining callback key binding.
Strict browser-owner cookies survive ordinary entry from an external site. An inert same-origin recovery page rechecks the existing cookie before an empty owner can be created; recovery attempts are bounded and preserve the application query.
DPoP authentication challenges use bounded parsing instead of repeatedly copying long header values. Oversized headers and excessive challenge parts fail explicitly before nonce retries.
Custom-cache diagnostics now match the documented remove() contract:
state consumption verifies deletion independently of its return value.
ID tokens and OIDC UserInfo reject malformed standard profile claim types,
including name/email strings, numeric updated_at, and address objects and
their standard members. Custom extension claims remain available.
HMAC key-length checks use the UTF-8 bytes used for signing and verification, consistently across client authentication, Request Objects, ID tokens, JARM, endpoint negotiation, and OAuth 2.1 configuration assessments.
Signed JWT profiles reject explicitly null kid and typ headers while
continuing to permit those optional fields to be absent.
Redirect regression tests observe target requests across the server process boundary and verify their counters with enabled 302, 307, and 308 redirects.
Prebuilt authenticated resource requests reject curl request_target
overrides, keeping the checked URL and DPoP URI aligned with the wire target.
Authenticated requests reject malformed HTTP headers and prohibited controls. Managed connections enforce protected headers after syntax validation; DPoP resource requests reject Host overrides.
Protocol JSON rejects escaped NUL characters and unpaired Unicode surrogates before decoding, preserving exact claim and member-name comparisons.
mTLS registration preserves explicit RFC 4514 subject DNs verbatim, including escaped trailing spaces, while retaining SAN normalization and input checks.
The future async backend now requires promises >= 1.3.3 in dependency
metadata and runtime checks. Older versions either lack future_promise()
or use a dispatch API incompatible with current versions of future.
Signed UserInfo key rotation now uses the client's TLS policy for both JWKS refresh throttling and fetching, so clients sharing a cache cannot consume another TLS policy's refresh allowance.
Independent endpoint credentials no longer inherit ID-token HMAC key-length or Request Object signing requirements. Introspection, revocation, and PAR continue to validate their own authentication credentials and algorithms.
Use your Shiny app's login with external R packages, SDKs and database drivers.
oauth_module_server() adds connection(). Its connections, and those from
oauth_connections_server(), offer $access_token() to get a current bearer
token, refreshing it when needed. $has_scopes() helps apps enable features
based on available permissions. See vignette("external-integrations") for examples.
Access several APIs through one login by configuring token_targets in
oauth_client(). Apps can select which API's token to use without asking users
to sign in separately for each API. Supported providers use RFC 8707 resource
indicators or Microsoft token acquisition. See vignette("token-targets").
Connections can refresh tokens before making an API call with
$request(refresh = TRUE), reducing failures caused by expired tokens.
This option does not automatically retry a failed API call.
oauth_module_server() adds reauthorize(), and
oauth_connections_server() adds reauthorize(connection_id), for a
"sign in again" flow that keeps previously reduced permissions.
Microsoft apps can skip fetching a Graph user profile with
oauth_provider_microsoft(userinfo_required = FALSE), allowing sign-in when
the API token is intended for another service. Exported Microsoft tokens can
include more permissions than the connection allows within the app.
When using token_targets with oauth_module_server(), set
reauth_after_seconds if your app needs a time-limited login. By default,
the login can stay active for the rest of the Shiny session even after all API
tokens expire. API calls still require a valid token.
Updated the missing browser-setup warning and UI documentation to recommend
oauth_ui() with the module ID and client, or the appropriate form-post or
connection-manager wrapper. These wrappers include use_shinyOAuth() setup.
Made direct-browser deployment guidance more prominent. The Shiny module now uses Posit Connect / Connect Cloud environment markers to print a specific reminder once per R process to open the direct app URL outside the dashboard's embedded preview.
Added provider helpers for ORCID (production and sandbox), GitLab (hosted and self-managed), Amazon Cognito, Globus Auth, Hugging Face, SURFconext (production and test), and authentik.
scope response when requesting no scopes,
preventing token exchange parse errors. Empty grants remain explicit
during refresh and never inherit prior scopes.Added optional connections for apps using one or more OAuth authorizations:
oauth_connection() combines a module's current token with the client's
approved resource_bases and required_scopes. Its $request() method
uses refreshed credentials and restricts calls to those APIs; status and
selected identity information are available without exposing credentials.oauth_connections(), with oauth_connections_ui() and
oauth_connections_server(), manages separate authorizations, including
repeated logins to the same provider. Connections normally end with the
Shiny session; optional browser or local-account retention restores them
after navigation.oauth_connection_store_memory() stores encrypted credentials in one R
process. oauth_browser_owner() and oauth_account_owner() configure who
can restore them and for how long. Retention does not survive R restarts.$refresh(scopes = ...) can reduce permissions while preserving
required scopes. Later refreshes retain the reduction; widening requires
new authorization. Disconnect removes local access before optional remote
revocation. Refresh and revocation account for credentials shared across
connections.callback_policy = "issuer" or "shared_routes" permits shared callback
URLs with issuer validation; distinct routes remain the default and are
required for encrypted JARM. Ordinary OAuth modules can share the UI through
additional_clients.Added SMART on FHIR STU 2.2 app support:
smart_discover() reads metadata from an approved FHIR server;
smart_client() combines it with an existing public, secret-based, or
asymmetric app registration for standalone or EHR launch. It checks server
capabilities and permissions, requires S256 PKCE and TLS 1.2 or newer, and
enables signed user identity only when requested.smart_launch_route() accepts EHR launches for configured clients using
browser retention. SMART supports direct authorization with query or form
POST callbacks.smart_context() reports the current patient, encounter, and validated
user reference, with a revision for detecting context changes after refresh.
smart_patient() and smart_fhir_user() fetch the corresponding FHIR
resources only when authorized and within the configured server.initial_expires_in_fallback can provide
an explicit fallback for the initial response.OAuthToken exposes additional token response parameters in extra_fields.
Each successful refresh replaces that list; initial_extra_fields
preserves the initial response. Both retain nested values and explicit nulls
and are redacted when printed.
Added oauth_ui() to handle URL-based callbacks before the app UI loads.
Wrap the UI with the same id and client used by oauth_module_server().
Both oauth_ui() and oauth_form_post_ui() accept named client registries
for apps using multiple OAuth modules or providers.
Added check_oauth21(), a read-only assessment of client and provider
configuration against OAuth 2.1 draft 16. It reports configuration problems,
external prerequisites, and recommendations without making requests or changing
settings, including the effective TLS policy for SMART clients.
Browser bindings are isolated by origin, tab, login, and app/module and stay
out of URLs and bookmarks. Callbacks enforce the registered path and response
transport, preserve escaped paths, and reject oversized input before routing.
Shared callback state is encrypted and requires atomic storage.
oauth_form_post_ui() supports trusted-proxy URI resolution. New visitors
cannot evict retained connections or browsers with pending authorizations.
Added signed and encrypted authorization responses (JARM) through
response_mode = "jwt", "query.jwt", or "form_post.jwt" with
oauth_module_server(). Algorithms can be configured or discovered;
jarm_max_lifetime defaults to 600 seconds. Malformed claims and headers are
rejected. handle_callback() continues to accept only classic callbacks.
authorization_server_mode supports multiple authorization servers through
validated issuer responses or distinct registered callback routes.
compare_callback_issuer can check a supplied issuer without requiring older
providers to send one; required issuer and JARM checks remain enforced.
Function argument names are more consistent. CRAN 0.5.0 argument names and positions remain supported, as do the existing S7 property names:
client and token.handle_callback() accepts callback state as state; introspection and
revocation helpers select access or refresh tokens with token_kind.id_token_allowed_algs for
ID-token signing algorithms. Clients use introspection_checks for extra
introspection requirements.client_assertion_*, mtls_*, request_object_*, and jarm_* names.
See ?oauth_client and ?oauth_provider for the current arguments.oauth_module_server() uses refresh_check_interval_ms to make the
polling interval's millisecond unit explicit.Client and provider configuration changes:
OAUTH_CLIENT_ID or
OAUTH_CLIENT_SECRET; an omitted secret is treated as absent. Public clients
must use PKCE. Printed objects redact credentials and sensitive URL components.endpoint_auth configures authentication separately for PAR, introspection,
and revocation, including credentials, assertions, mTLS, and headers.
Discovered authentication methods and algorithms are negotiated per endpoint.client_assertion_typ supports JWT and client-authentication+jwt,
including per-endpoint overrides.Provider configuration changes:
infer_oidc_from_issuer = FALSE allows generic OAuth issuer validation
without enabling OIDC; the default remains TRUE.jwks_uri overrides discovery across signing-key consumers.
Missing or disallowed required key locations fail during configuration.userinfo_allowed_algs configures signed UserInfo algorithms independently
from ID-token algorithms, with corresponding discovery support.allow_missing_token_type = TRUE explicitly permits Bearer-only providers
that omit the required token type. The default remains strict; the fallback
does not apply to DPoP clients or replace invalid values.Provider helper updates:
oauth_provider_apple() and oauth_client_secret_apple() for Apple
sign-in. Validated Apple email_verified strings become logical values,
including in claim checks and id_token_claims.oauth_provider_microsoft() rejects unrecognized tenants instead of
silently disabling OIDC. Use a directory GUID or a supported tenant alias;
OAuth-only operation requires explicit id_token_validation = FALSE.oauth_provider_slack(profile = ...) supports "confidential" and
"public_pkce" registrations.account_id. Migrate stored id mappings and
audit digests from an authenticated profile; allow_legacy_id = TRUE permits
a temporary fallback when account_id is absent.oauth_provider_oidc() respects its authentication-style override.OIDC discovery accepts an issuer or its standard discovery URL, preserves
the exact issuer and endpoint paths, and requires an exact issuer match.
Required metadata and URL syntax are validated; optional capability arrays
may be empty but cannot be null or malformed. HTTPS is required unless
shinyOAuth.allow_insecure_oidc_loopback = TRUE permits loopback development.
Endpoints may use other hosts subject to shinyOAuth.allowed_hosts; JWKS
retains its separate host policy. Transport failures identify the attempted
discovery URL and underlying error.
JWKS handling rejects malformed keys and inconsistent key usage, while allowing unrelated public extensions. Discovery preserves escaped issuer paths and enforces current host and pinning policies. Caching respects HTTP freshness and no-store directives; rotation works when a provider reuses a key ID. RSA keys require at least 2048 bits and a trusted algorithm choice: unlabelled keys use RS256 unless a single RSA algorithm is configured. Generic OAuth discovery can fall back to OIDC discovery locations.
Ed25519 keys support both EdDSA and Ed25519 for verification, client
assertions, Request Objects, and DPoP. Outbound RSA signing also supports
explicitly selected RS384; RS256 remains the default.
OIDC claim validation consistently checks standard and optional claim types
in ID tokens and UserInfo, including refreshed nonces. UserInfo must match the
validated ID token's sub; additional audiences require explicit trust and
multiple audiences require the correct azp. Future authentication times are
rejected, and requested claim objects compare independently of member order.
JSON profile arrays retain their existing vector and data-frame structure.
Authentication deadlines are not extended by refresh. Token lifetimes are calculated conservatively, and long lifetimes and future timestamps avoid integer overflow; invalid lifetime settings are rejected. Concurrent refreshes are combined, stale results are ignored, and the login ID token remains the continuity baseline. Refresh credentials are retired after potentially consuming failures. Ordinary OAuth refreshes explicitly request known retained scopes so earlier reductions are preserved.
DPoP-bound tokens, including opaque tokens, work consistently with UserInfo
and resource helpers and cannot be downgraded to Bearer. Proofs preserve the
actual request path, and retries use fresh nonces from intermediate responses.
The nonce limit defaults to 4096 bytes, configurable with
shinyOAuth.dpop_nonce_max_bytes; oversized responses report a clear error.
Malformed or conflicting token-binding thumbprints are rejected.
Authorization request, JAR, and PAR improvements:
authorization_method = "POST" supports long browser requests when the
provider supports it; SMART requires advertised authorize-post. Shiny
submits the form automatically. prepare_authorization_request() returns
the URL, method, and form fields for custom browser navigation.max_age through client properties or
extra_auth_params, not the endpoint URL; policy applies across transports.oauth_ui().
Encryption accepts public PEM keys and honors key metadata, size, and pins,
with one refresh on key-selection failure and hardened decryption checks.mTLS improvements:
mtls_require_observed_cnf defaults to TRUE. For server-enforced opaque
tokens, keep mtls_certificate_bound_access_tokens = TRUE and set it to
FALSE; any observed binding must still match.Token, introspection, and HTTP handling validates credentials, media types,
response sizes, activity, and scopes before use. Form encoding is consistent,
and supported parameter overrides replace existing values. Resource helpers
preserve escaped paths when adding queries, reject competing credentials and
inherited authentication/cache/retry policies, and support per-call
resource_hosts.
Method-changing curl options and HEAD requests with bodies are rejected to
prevent unsafe retries and incorrect DPoP proofs; configure methods with
httr2::req_method(). Retries honor bounded Retry-After values. Invalid
numeric retry/assertion options use safe defaults, and URL-check flags must
be logical. shinyOAuth.tls_min_version can require TLS 1.2 or 1.3 across
package requests and async workers.
Audit and OpenTelemetry output is safer by default:
unknown;
diagnostic text is literal.shiny_session$session_token_digest; raw tokens require
shinyOAuth.audit_include_raw_session_token = TRUE. Authorization details
require shinyOAuth.otel_include_authorization_details = TRUE.The Cloud Run example uses pinned deployment dependencies. Spotify examples
handle remote content safely and fix playback formatting; the dashboard is
installed as examples/spotify-dashboard.R.
Async use requires mirai >= 2.5.1; the future_promise() fallback uses
parallel-safe random-number generation.
Added mutual-TLS ('mTLS', RFC 8705) support, including mTLS client
authentication, certificate-bound access tokens, mTLS endpoint aliases, and
the exported oauth_client_mtls_registration() helper for RFC 8705 client
metadata.
Added Demonstrating Proof-of-Possession ('DPoP', RFC 9449) support.
Clients configured with dpop_private_key now send DPoP proofs on token and
protected-resource requests, include dpop_jkt in authorization requests,
can require token_type = "DPoP" plus cnf.jkt binding, and replay one
DPoP-Nonce challenge on token and protected-resource requests.
Added JWT-Secured Authorization Request ('JAR', RFC 9101) support.
oauth_client() can now send signed and encrypted Request Objects via
request_object_mode = "request" (sent as parameter) or
via request_object_mode = "request_uri" (served from your Shiny app).
Added Pushed Authorization Request ('PAR', RFC 9126) support.
Providers can now configure par_url directly or pick it up from OIDC
discovery, and login flows will push the authorization request and redirect
with the returned request_uri.
Added response_mode = "form_post" support for authorization-code
callbacks. Apps can wrap their UI with oauth_form_post_ui() so Shiny accepts
the provider POST, stores the callback server-side under a one-time handle, and
lets oauth_module_server() finish the existing state, issuer, and token
exchange flow. Stored callback handles are bounded by the effective state/store
TTL and the shinyOAuth.callback_max_form_post_* size-cap options.
Added OpenTelemetry ('OTel') support (using the 'otel' package).
'shinyOAuth' now emits OTel logs from existing audit events and traces
key OAuth operations such as module initialization, login/callback handling,
token exchange/refresh, userinfo/introspection/revocation, and session-end
cleanup. See vignette("opentelemetry", package = "shinyOAuth") for more
information. The span catalog includes form-post callback state consumption
and documents the opt-in policy for exception messages.
Observability and audit logging improvements:
trace_id across redirect issuance, callback
validation, token exchange, and login outcome events, making it easier to
correlate the pre-redirect and post-redirect Shiny sessions for a single login
round-trip; async work also carries more accurate originating Shiny
session/process context into worker-emitted events.audit_login_success$sub_source = "id_token" now reflects the
OAuthToken@id_token_validated result for the returned token, so telemetry no
longer overstates ID-token validation when tests or debug options skip
signature verification.remote_addr as well as proxy
headers, so default audit events no longer export raw client IP addresses.audit_token_exchange and
audit_token_refresh now include expires_in_synthesized, indicating that
the provider did not return a usable expires_in and shinyOAuth had to
synthesize one; audit_login_failed now distinguishes async
payload-validation and state-store-lookup failures from async token-exchange
failures; audit_userinfo distinguishes missing sub and JWT/JWKS validation
failures; and error-state consumption events use the logical state digest when
available for better correlation. See
vignette("audit-logging", package = "shinyOAuth") for more information.options(shinyOAuth.trace_hook = ...) is no longer treated as a separate
documented event sink. Prefer options(shinyOAuth.audit_hook = ...); the
old trace_hook option now remains only as a backward-compatible alias when
audit_hook is unset.shinyOAuth.print_errors /
shinyOAuth.print_traceback options. Internal console error logging
now uses explicit internal flags instead of package-wide option fallbacks.http_error audit events now omit raw provider oauth_error_description
text by default and keep only oauth_error, oauth_error_uri, and
body_digest. The raw description is emitted only when
options(shinyOAuth.expose_error_body = TRUE) is enabled for debugging.err_http() now strips query strings, fragments, and userinfo from
response URLs before surfacing them through condition messages and emitted
events, reducing leakage of authorization codes, state, request URIs, and
similar URL-borne secrets.oauth_module_server() now:
revoke_on_session_end = TRUE but the
provider does not expose a revocation_url, instead of crashing while
formatting that configuration error.?error=... handling until the browser
token is available and treating browser-token mismatches as invalid_state
instead of surfacing provider-controlled error text.oauth_client(introspect = TRUE) to its proactive refresh path, so
proactive refresh now follows the same refresh-time token introspection
policy as direct refresh_token(..., introspect = TRUE) calls.invalid_state in its callback error state for
CSRF/state/browser-token validation failures instead of flattening those paths
into token_exchange_error.error_uri values unless they are absolute HTTPS URLs, so
unsafe schemes like javascript: are no longer surfaced through
values$error_uri.browser_cookie_path more strictly, requiring a leading / and
rejecting semicolons or control characters so unsafe cookie attributes cannot
be injected through the configured cookie Path.OAuthToken and OAuthClient now print with redacted token/secret/key
previews instead of exposing full credential material in default console output.
OAuthToken now tracks normalized granted_scopes plus
granted_scopes_verified, so apps can distinguish between scope sets that were
explicitly returned and ones that were carried forward when the provider omitted
scope. Refresh now preserves prior granted scopes instead of widening back to
the client's configured scopes when a refresh response omits scope.
oauth_client() (OAuthClient) now:
pkce_code_verifier
and nonce fields when the provider does not require them, while still
rejecting missing PKCE or nonce values when those checks are enabled.enforce_callback_issuer = TRUE to require the RFC 9207 iss
callback parameter for shared-redirect multi-issuer deployments. Relatedly,
handle_callback() now accepts iss, so advanced callers building around
prepare_call() can supply the callback issuer and get the same client-level
RFC 9207 check before token exchange.enforce_callback_issuer unset and the provider advertises
authorization_response_iss_parameter_supported = TRUE.resource support, so authorization, token exchange,
and refresh requests can request audience-restricted tokens without dropping
down to manual extra params.scope_validation to "warn", so RFC-compliant reduced grants
surface as warnings unless you opt into scope_validation = "strict".values
entry without I(...), because jsonlite::toJSON(auto_unbox = TRUE) would
otherwise serialize that OIDC array constraint as a scalar.value and values constraints when
claims_validation is enabled, not just presence of essential = TRUE
claims.claims_validation = "warn" when callers request essential or
value-constrained claims and do not set claims_validation explicitly,
so claim mismatches are surfaced by default unless callers opt out with
claims_validation = "none".openid is auto-added to the authorization request, the sealed state payload,
token-response scope validation, and introspection scope validation now use
that same effective scope set.sub for introspect_elements = "sub" before
falling back to userinfo, so unvalidated ID token payloads no longer anchor
the introspection subject check.invalid_state instead of resuming under a
different worker policy.oauth_provider() (OAuthProvider) now:
response_mode = "query" and response_mode = "form_post" for
authorization-code callbacks, while still rejecting unsupported modes such as
"fragment". When provider metadata advertises response_modes_supported,
shinyOAuth also fails fast if an explicit response mode is not advertised.userinfo_id_token_match, and shinyOAuth now always binds userinfo to a
validated ID token subject when that baseline exists.shinyOAuth_input_error conditions for malformed constructor
inputs such as vector endpoint URLs or empty discovery-helper domains, so
apps can trap provider validation failures consistently.jwks_cache$get() signatures without calling the cache
during construction, avoiding side effects in duck-typed cache backends.allowed_algs against shinyOAuth's actual inbound verifier
support and no longer accepts RSA-PSS (PS256, PS384, PS512) entries,
which were previously present in the generic helper's defaults despite
lacking verifier support. Older configs that explicitly allowed those
algorithms must switch to supported verifier algorithms.refresh_token parameter name in extra_token_params to
prevent duplicate refresh-token parameters during token refresh requests.oauth_provider_oidc_discover() now:
S256. shinyOAuth keeps S256 as the default and only allows a downgrade to
plain when you pass pkce_method = "plain" explicitly.jwks_uri but the selected
policy still needs signing keys, including id_token_validation = TRUE,
nonce-enabled OIDC flows, and signed UserInfo JWT validation. These
misconfigurations now fail during provider setup instead of later during a
JWKS fetch.jwks_uri values against the same absolute-URL,
scheme, and host policy used for other discovery endpoints, so invalid or
disallowed JWKS URLs now fail during discovery instead of later during the
first JWKS fetch.jwks_host_allow_only during its early jwks_uri host check, so
explicitly pinned cross-host JWKS endpoints no longer require disabling
issuer-host matching.token_endpoint_auth_methods_supported = ["none"] to a distinct public
token auth style that never sends client_secret, even when oauth_client()
picks one up from OAUTH_CLIENT_SECRET.oauth_provider_oidc() now trims trailing slashes from base_url before
deriving endpoint URLs and the configured issuer, avoiding valid ID tokens
being rejected on a strict OIDC iss comparison when the helper was configured
with a URL like https://issuer.example/.
oauth_provider_microsoft() no longer drops the Microsoft alias tenants to
OAuth 2.0 plus userinfo identity by default. common and organizations now
validate ID tokens using Microsoft's tenant-independent issuer and signing-key
issuer rules, and consumers now validates against the stable consumer tenant
issuer.
validate_id_token() now properly rejects auth_time claims set in the
future (beyond leeway). Previously, a future auth_time produced a negative
elapsed value that always passed the max_age freshness check.
introspect_token() now uses provider@userinfo_id_selector consistently
when it checks the authenticated subject against fetched UserInfo data, and now
fails closed on malformed introspection JSON. Non-object responses are rejected
instead of being normalized from the first parsed element.
refresh_token() now:
id_token from refresh responses.introspect = TRUE, enforces token introspection as a
hard policy check instead of best-effort metadata enrichment. Refresh now
fails when introspection is unsupported, inactive, malformed, or missing
required introspect_elements such as sub, client_id, or scope.get_userinfo() now:
alg compatibility checks to signed UserInfo JWT
verification as ID token verification, rejecting JWKS keys that advertise a
different algorithm even if signature verification would otherwise succeed.sub claim in userinfo responses from OIDC
providers (those with an issuer configured), per OIDC Core section 5.3.
Previously, a non-compliant response without sub could be accepted if
userinfo_id_token_match was not enabled. The signed-JWT path
(validate_signed_userinfo_claims()) also now checks sub alongside the
existing iss/aud validation.Signed UserInfo JWT validation now:
exp, iat, and nbf when
those temporal claims are present, rejecting expired or not-yet-valid UserInfo
JWT responses instead of accepting them based only on
signature/issuer/audience. oauth_client() can also require specific UserInfo
JWT temporal claims to be present via userinfo_jwt_required_time_claims.jose::jwt_decode_sig(), so EdDSA
UserInfo JWTs can verify correctly, provider leeway is honored
consistently, and invalid typ headers are rejected.Successful token and refresh responses now always require token_type, even
when allowed_token_types = character(). An empty allowlist still disables
value allowlisting, but it no longer waives the RFC-required field.
Refreshed OIDC ID tokens now enforce full continuity for auth_time,
refresh-time nonce, and azp in addition to the existing iss / sub /
aud checks.
Token exchange and refresh requests no longer retry on transport errors or
transient HTTP statuses (408/429/5xx). Authorization codes are single-use and
refresh tokens may be rotated on each use; retrying after the server has
already committed the first request would replay an invalidated credential,
causing invalid_grant errors or triggering refresh-token replay detection.
Hardened runtime JWKS discovery by validating the discovery issuer before
trusting jwks_uri. This policy is now stored on OAuthProvider via
issuer_match, so both provider discovery and runtime JWKS fetches apply the
same rule: url for exact issuer URL matching, host for scheme-and-host
matching, or none to skip the discovery issuer check.
JWKS caching now respects global host policy immediately. Cached
entries are scoped to the current allowed_hosts /
allowed_non_https_hosts settings, and cache hits re-check the stored
jwks_uri before a JWKS is trusted.
Scope validation now treats an omitted scope in the initial token response
as unchanged from the requested scope, matching RFC 6749 section 5.1 instead
of rejecting otherwise compliant authorization servers by default.
Strict token-response and introspection scope validation now treats commas as
part of a single scope token, matching RFC 6749 instead of splitting
scope = "read,write" into separate read and write scopes.
Missing expires_in values now default to a finite 3600-second fallback
rather than an effectively indefinite session. Override this with
options(shinyOAuth.default_expires_in = <seconds>), and use
oauth_module_server(reauth_after_seconds = ...) when you need a stricter
session-age cap.
err_http() now guards against oversized HTTP error bodies before hashing
or JSON parsing, so large chunked or misleading error responses now trip the
existing body-size limit consistently.
at_hash validation now resolves EdDSA from the verified signing key/JWK
instead of guessing from alg alone: Ed25519 uses the exact SHA-512
mapping, while signature-skipped or currently unsupported EdDSA curves
fail closed.
Deprecated error_on_softened(). It remains a narrow guard for a few
dev/debug softeners, but the docs now stop presenting it as a comprehensive
deployment-hardening check and show explicit option checks instead.
Renamed the resource-request helpers to resource_req() and
perform_resource_req(). The existing public client_bearer_req() name
remains available as a deprecated alias, and perform_client_bearer_req() is
also exported as a deprecated compatibility alias.
perform_resource_req() is a new function which builds and performs an
authenticated resource-request and, for DPoP-bound access tokens, replays one
use_dpop_nonce challenge with the server-provided nonce. It can also
take pre-existing 'httr2' request objects and layer authentication and DPoP on
top.
'mirai' & async backend improvements:
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_hook functions: warnings, messages, and errors
(surfaced as warnings) all propagate back to the main thread. Replay can be
disabled via options(shinyOAuth.replay_async_conditions = FALSE).state_store / JWKS cache backends)
into the worker context. The state_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.mirai::daemons_set() instead of
mirai::status(). Falls back to mirai::info() on older 'mirai' versions
that lack mirai::daemons_set() (< 2.3.0).options(shinyOAuth.async_timeout)
(milliseconds); timed-out 'mirai' tasks are automatically cancelled by the
dispatcher. Default is NULL (no timeout).mirai_error_type field. This classifies
mirai transport-level failures separately from application-level errors.ID token validation (validate_id_token()):
crit) processing rules. Tokens containing unsupported critical
extensions are rejected with a shinyOAuth_id_token_error. The current
implementation supports no critical extensions, so any crit presence
triggers rejection.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. New id_token_at_hash_required property on
OAuthProvider (default FALSE) forces login to fail when the ID token does
not contain an at_hash claim.iss
and aud claims 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.shinyOAuth_id_token_error instead of
letting a confusing alg/typ/parse failure propagate.auth_time claim when max_age is
present in extra_auth_params (OIDC Core section 3.1.2.1).exp - iat);
tokens with unreasonably long lifetimes are rejected with a
shinyOAuth_id_token_error. Configure via
options(shinyOAuth.max_id_token_lifetime = <seconds>) (default of 86400
which is 24 hours). Set to Inf to disable the check.Stricter state store usage:
custom_cache() gains an optional take parameter 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).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
setting options(shinyOAuth.allow_non_atomic_state_store = TRUE), which
downgrades the error to a warning.OAuthClient validator now validates $take() signature when present.$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: OAuthClient now rejects redirect URIs containing
fragments (per RFC 6749, section 3.1.2); OAuthProvider now rejects issuer
identifiers containing query or fragment components, covering both
oauth_provider_oidc_discover() and manual construction of providers.
Stricter state payload parsing: callback state now 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 when Content-Length exceeds the limit; a
post-download guard catches chunked responses. Default 1 MiB, configurable via
options(shinyOAuth.max_body_bytes).
OAuthProvider (S7 class):
leeway validator 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.extra_auth_params and
extra_token_params is now case-insensitive and trims whitespace.pkce_method and 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):
claims_validation property; when the client sends a structured
claims request parameter with essential = TRUE entries, this setting
controls whether the returned ID token and/or userinfo response are checked
for those essential claims (similar to scope_validation).required_acr_values property; enables client-side
enforcement of the OIDC acr (Authentication Context Class Reference) claim.extra_token_headers are 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.client_assertion_alg and client_assertion_audience values
(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 "" for client_assertion_audience is now explicitly rejected instead
of being silently treated as "not provided".OAuthToken (S7 class):
id_token_claims property 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.id_token_validated property (logical) indicating whether the ID
token was cryptographically verified during the OAuth flow.oauth_module_server():
error_uri from provider error
callbacks (RFC 6749, section 4.1.2.1). The new $error_uri reactive field
contains the URI to a human-readable error page when the provider includes
one; NULL otherwise. The error_uri callback parameter is also validated
against a configurable size limit
(e.g., options(shinyOAuth.callback_max_error_uri_bytes = 2048))..process_query(), ensuring more consistent
cleanup..process_query() called .query_has_oauth_callback_keys() (which parses
the query string) before any size validation, bypassing the intended DoS
guardrails. The validate_untrusted_query_string() check now runs
unconditionally at the top of .process_query().?error=...) now
require a valid state parameter. Missing/invalid/consumed state is then
treated properly as an invalid_state error instead of surfacing the error
from ?error=... (which could be set by an attacker).iss query parameter now validate this against
the provider's configured/discovered issuer during callback processing
(complementing the existing ID token iss claim validation that occurs
post-exchange) (per RFC 9207). A mismatch produces an issuer_mismatch error
and audit event, defending against authorization-server mix-up attacks in
multi-provider scenarios. When iss is absent, current behavior is retained
(no enforcement).handle_callback(): no longer accepts decrypted_payload and
state_store_values bypass parameters. These parameters were only intended for
internal use by oauth_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 phase (exchange_code /
refresh_token) so operators know that proactive token refresh will not
trigger. Users can now also set a finite default lifetime for such tokens via
options(shinyOAuth.default_expires_in = <seconds>); when unset, shinyOAuth
now falls back to 3600 seconds.
get_userinfo() now supports JWT-encoded userinfo responses per OIDC Core,
section 5.3.2. When the endpoint returns Content-Type: application/jwt, the
body is decoded as a JWT. Verification is fail-closed: signature verification is
always performed against the provider JWKS using the provider's allowed_algs,
alg=none is always rejected, and unparseable headers, non-asymmetric
algorithms, or missing issuer/JWKS infrastructure all raise errors.
options(shinyOAuth.allow_unsigned_userinfo_jwt = TRUE) permits unsigned
JWTs. New userinfo_signed_jwt_required property on OAuthProvider
(default FALSE) mandates that the userinfo endpoint returns
application/jwt content-type which is then subject to the above verification.
client_bearer_req() now validates the target URL against is_ok_host()
before attaching the Bearer token. Relative URLs, plain HTTP to non-loopback
hosts, and hosts outside options(shinyOAuth.allowed_hosts) are rejected by
default. A new check_url argument (default TRUE) allows opting out of
the check when the URL has already been validated.
err_http() now extracts RFC 6749 section 5.2 structured error fields
(error, error_description, error_uri) from JSON error response bodies.
These fields are surfaced in the error message bullets, attached to the
condition object (as oauth_error, oauth_error_description,
oauth_error_uri), and included in trace/audit events. This improves debugging
of token endpoint failures (e.g. invalid_grant, invalid_client) without
changing existing control flow.
OIDC claims parameter support (OIDC Core, section 5.5): OAuthClient and
oauth_client() now accept a claims argument to request specific claims
from the userinfo Endpoint and/or in the ID token. Pass a list structure
(automatically JSON-encoded) or a pre-encoded JSON string.
OIDC openid scope enforcement: when a provider has an issuer set
(indicating OIDC) and openid is missing from the client's scopes,
build_auth_url() now auto-prepends it and emits a one-time warning.
OIDC discovery (oauth_provider_oidc_discover()) now prefers confidential
auth methods (client_secret_basic, client_secret_post) over none when
both are advertised in token_endpoint_auth_methods_supported. Previously,
mixed metadata (e.g. none + client_secret_basic) with PKCE enabled would
silently select the public-client posture ("body" without credentials).
Scope validation now aligns with the RFC 6749, section 3.3 scope-token
grammar (NQSCHAR = %x21 / %x23-5B / %x5D-7E). The previous regex rejected
valid ASCII characters such as !, #, $, =, @, ~, and others. All
printable ASCII except space, double-quote, and backslash is now accepted.
JWT helpers (build_client_assertion(),
resolve_client_assertion_audience()) now have defense-in-depth scalar guards
so malformed property values cannot cause subscript errors at runtime.
Audit events:
audit_token_refresh: replaced non-informative had_refresh_token field
(always TRUE post-mutation) with refresh_token_rotated (indicates whether
the provider returned a new refresh token).Async backend: the default async backend is now 'mirai' (>= 2.0.0) for
simpler and more efficient asynchronous execution. Use mirai::daemons() to
configure async workers. A 'future' backend configured with future::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.
Token revocation: tokens can now be revoked when Shiny session ends. Enable
via revoke_on_session_end = TRUE in oauth_module_server(). The provider must
expose a revocation_url (auto-discovered for OIDC, or set manually via
oauth_provider()). New exported function revoke_token().
Token introspection on login: validate tokens via the provider's introspection
endpoint during login. Configure via introspect and introspect_elements
properties on OAuthClient. The provider must expose an introspection_url
(auto-discovered for OIDC, or set manually via oauth_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 via options(); 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_pins with jwks_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 via options(shinyOAuth.allow_redirect = TRUE). client_bearer_req()
also gains follow_redirect, which defaults to FALSE, 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.
OAuthProvider now requires absolute URLs (scheme + hostname) for all
endpoint URLs.
Provider fingerprint now includes userinfo_url and introspection_url,
reducing risk of misconfiguration when multiple providers share endpoints.
state_payload_max_age property on OAuthClient for independent freshness validation
of the state payload's issued_at timestamp.
Default client assertion JWT TTL reduced from 5 minutes to 120 seconds, reducing the window for replay attacks while allowing for clock skew.
New audit events: session_ended (logged on Shiny session close),
authenticated_changed (logged when authentication status changes),
token_introspection (when introspect_token() is used), token_revocation
(when revoke_token() is used), error_state_consumed and
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_success now includes sub_source to indicate whether the
subject digest came from userinfo, id_token (verified), or id_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) with options(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.
Configurable scope validation: validate_scopes property on OAuthClient
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_in values in token responses.
Added warning when OAuthClient is 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 OAuthProvider uses HS* algorithm but
options(shinyOAuth.allow_hs = TRUE) is not enabled; also immediate error when OAuthProvider
uses HS* algorithm and ID token verification can happen but client_secret is
absent or too weak.
build_auth_url() now uses package-typed errors (err_invalid_state())
instead of generic stopifnot() assertions, ensuring consistent error
handling and audit logging.
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_ops is present on keys, only keys with key_ops
including "verify" are considered.
oauth_provider() now defaults allowed_token_types to c("Bearer") for all
providers. This prevents accidentally misusing non-Bearer tokens (e.g., DPoP,
MAC) as Bearer tokens. Set allowed_token_types = character() to opt out.
Token type is also now validated before calling the userinfo endpoint.
client_assertion_audience property on OAuthClient allows overriding the
JWT audience claim for client assertion authentication.
Package now correctly requires httr2 >= 1.1.0.
authenticated now flips to FALSE promptly when a token expires or
reauth_after_seconds elapses, even without other reactive changes. Previously,
the value could remain TRUE past 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 using async = TRUE. Previously, options set
in the main process (including audit_hook, trace_hook, HTTP settings, and
any custom options) were not available in future::multisession workers.
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_at validation now applies clock drift leeway (from
OAuthProvider@leeway / shinyOAuth.leeway option), consistent with ID token
iat check.
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).
Rewrote vignette("authentication-flow") to improve clarity.
Skip timing-sensitive tests on CRAN.