| Title: | OIDC Authentication and OAuth Authorization for 'shiny' Applications |
| Version: | 0.6.0 |
| Description: | Provides a simple, configurable framework for 'OpenID Connect' (OIDC) authentication and 'OAuth 2.0' authorization in 'shiny' applications using 'S7' classes. Defines providers, clients, and tokens, as well as various supporting functions and a 'shiny' module. Features include cross-site request forgery (CSRF) protection, state encryption, 'Proof Key for Code Exchange' (PKCE) handling, validation of OIDC identity tokens (nonces, signatures, claims), automatic user info retrieval for OIDC and supported 'OAuth' providers, asynchronous flows, and hooks for audit logging. |
| License: | MIT + file LICENSE |
| Encoding: | UTF-8 |
| Imports: | S7 (≥ 0.2.0), R6 (≥ 2.0), rlang (≥ 1.0.0), shiny (≥ 1.7.0), jsonlite (≥ 1.0), openssl (≥ 2.0.0), httr2 (≥ 1.1.0), curl, urltools (≥ 1.7.3), cachem (≥ 1.1.0), jose (≥ 1.2.0), lifecycle (≥ 1.0.0), cli (≥ 3.0.0), htmltools (≥ 0.5.0), otel (≥ 0.2.0) |
| Suggests: | bslib, ggplot2, purrr, dplyr, testthat (≥ 3.2.2), DT, knitr, rmarkdown, webfakes, promises, mirai (≥ 2.5.1), future, withr, later, callr, processx, pkgload, chromote, sodium, shinytest2, xml2, otelsdk |
| Depends: | R (≥ 4.1.0) |
| Config/testthat/edition: | 3 |
| VignetteBuilder: | knitr |
| URL: | https://github.com/lukakoning/shinyOAuth, https://lukakoning.github.io/shinyOAuth/ |
| BugReports: | https://github.com/lukakoning/shinyOAuth/issues |
| Config/roxygen2/version: | 8.0.0 |
| NeedsCompilation: | no |
| Packaged: | 2026-09-17 08:15:10 UTC; root |
| Author: | Luka Koning [aut, cre, cph] |
| Maintainer: | Luka Koning <koningluka@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-09-17 08:50:02 UTC |
shinyOAuth: OIDC Authentication and OAuth Authorization for 'shiny' Applications
Description
Provides a simple, configurable framework for 'OpenID Connect' (OIDC) authentication and 'OAuth 2.0' authorization in 'shiny' applications using 'S7' classes. Defines providers, clients, and tokens, as well as various supporting functions and a 'shiny' module. Features include cross-site request forgery (CSRF) protection, state encryption, 'Proof Key for Code Exchange' (PKCE) handling, validation of OIDC identity tokens (nonces, signatures, claims), automatic user info retrieval for OIDC and supported provider-specific 'OAuth' integrations, asynchronous flows, and hooks for audit logging.
Author(s)
Maintainer: Luka Koning koningluka@gmail.com [copyright holder]
Authors:
Luka Koning koningluka@gmail.com [copyright holder]
See Also
Useful links:
Report bugs at https://github.com/lukakoning/shinyOAuth/issues
OAuthClient S7 class
Description
An OAuthClient holds your app's registration with a provider: its client ID,
credentials, return address, and requested permissions. It also holds the
pending login state and client-specific token validation settings used by
the Shiny module and token helpers. Create it with oauth_client(), which
resolves defaults from the provider and the supplied client settings.
Usage
OAuthClient(
provider = NULL,
client_id = character(0),
client_secret = character(0),
client_assertion_private_key = NULL,
client_assertion_private_key_kid = NA_character_,
client_assertion_alg = NA_character_,
client_assertion_audience = NA_character_,
mtls_client_cert_file = NA_character_,
mtls_client_key_file = NA_character_,
mtls_client_key_password = NA_character_,
mtls_client_ca_file = NA_character_,
mtls_certificate_bound_access_tokens = FALSE,
request_object_mode = "parameters",
response_mode = NA_character_,
request_object_signing_alg = NA_character_,
request_object_audience = NA_character_,
request_object_encryption_alg = NA_character_,
request_object_encryption_enc = NA_character_,
request_object_encryption_kid = NA_character_,
request_object_ttl = 45,
request_object_nbf_skew = NA_real_,
dpop_private_key = NULL,
dpop_private_key_kid = NA_character_,
dpop_signing_alg = NA_character_,
dpop_require_access_token = !is.null(dpop_private_key),
redirect_uri = character(0),
enforce_callback_issuer = FALSE,
scopes = character(0),
resource = character(0),
claims = NULL,
state_store = cachem::cache_mem(max_age = 300),
state_payload_max_age = 300,
state_entropy = 64,
state_key = random_urlsafe(n = 128),
scope_validation = "warn",
claims_validation = "none",
userinfo_jwt_required_time_claims = character(0),
required_acr_values = character(0),
introspect = FALSE,
introspection_checks = character(0),
endpoint_auth = list(),
authorization_server_mode = "single",
authorization_server_redirect_uris = character(0),
dpop_require_observed_cnf = FALSE,
jarm_signed_response_alg = NA_character_,
jarm_encrypted_response_alg = NA_character_,
jarm_encrypted_response_enc = NA_character_,
jarm_decryption_private_key = NULL,
jarm_decryption_private_key_kid = NA_character_,
jarm_max_lifetime = 600,
mtls_require_observed_cnf = TRUE,
trusted_id_token_audiences = character(0),
compare_callback_issuer = is_valid_string(provider@issuer) &&
(missing(enforce_callback_issuer) || isTRUE(enforce_callback_issuer)),
client_assertion_typ = "JWT",
resource_bases = character(0),
required_scopes = character(0),
label = default_client_label(provider),
authorization_method = "GET",
scope_policy = list(),
smart = list(),
introspect_elements = NULL,
client_private_key = NULL,
client_private_key_kid = NULL,
userinfo_jwt_required_temporal_claims = NULL,
mtls_request_certificate_bound_access_tokens = NULL,
tls_client_cert_file = NULL,
tls_client_key_file = NULL,
tls_client_key_password = NULL,
tls_client_ca_file = NULL,
authorization_request_mode = NULL,
authorization_request_signing_alg = NULL,
authorization_request_audience = NULL,
authorization_request_encryption_alg = NULL,
authorization_request_encryption_enc = NULL,
authorization_request_encryption_kid = NULL,
authorization_request_ttl = NULL,
authorization_request_nbf_skew = NULL
)
Arguments
provider |
The service configuration, created with a provider helper such as
|
client_id |
The identifier assigned when you register your app with the provider. |
client_secret |
The secret issued for your app, preferably read with
It is required for |
client_assertion_private_key |
Optional private key for |
client_assertion_private_key_kid |
Optional key identifier (kid) to include in the JWT header
for |
client_assertion_alg |
Optional JWT signing algorithm to use for client assertions.
When omitted, defaults to |
client_assertion_audience |
Optional override for the |
mtls_client_cert_file |
Optional path to the PEM-encoded client
certificate (or certificate chain) used for RFC 8705 mutual TLS (mTLS) client
authentication and certificate-bound protected-resource requests. Required
when |
mtls_client_key_file |
Optional path to the PEM-encoded private key used
with |
mtls_client_key_password |
Optional password used to decrypt an encrypted
PEM private key referenced by |
mtls_client_ca_file |
Optional path to a PEM CA bundle used to validate the remote HTTPS server certificate when making mTLS requests. This is mainly useful for local or test environments that use self-signed server certificates. |
mtls_certificate_bound_access_tokens |
Logical. Whether this
client intends to request RFC 8705 certificate-bound access tokens when
the provider advertises that capability. Default is Set this to Requires |
request_object_mode |
Controls how the authorization request is transported to the provider.
If the provider has a Use a signed Request Object when the provider requires JAR or when it must
verify the integrity of the authorization parameters. |
response_mode |
How the provider returns the login result. Leave Signed responses (JWT Secured Authorization Response Mode, JARM)
use |
request_object_signing_alg |
Optional JWS algorithm override for
signed authorization requests when |
request_object_audience |
Optional override for the |
request_object_encryption_alg |
Optional JWE key-management
algorithm override for encrypted Request Objects. Current outbound support
is limited to |
request_object_encryption_enc |
Optional JWE content-encryption
algorithm override for encrypted Request Objects. Current outbound support
is limited to the AES-CBC-HMAC family ( |
request_object_encryption_kid |
Optional key identifier ( |
request_object_ttl |
Positive number of seconds to keep signed
authorization request objects ( |
request_object_nbf_skew |
Optional non-negative number of
seconds. When provided, shinyOAuth adds an |
dpop_private_key |
Private key for tying tokens to this app's requests
using Demonstrating Proof of Possession (DPoP). Only needed when your
provider/API supports DPoP. Accepts an
|
dpop_private_key_kid |
Optional key identifier ( |
dpop_signing_alg |
Optional JWT signing algorithm to use for DPoP
proofs. When omitted, a compatible asymmetric default is selected based on
the private key type/curve (for example |
dpop_require_access_token |
Logical or |
redirect_uri |
The URL where users return after login. It must match the callback URL registered with your provider, including scheme, host, port, and path. Use HTTPS in production. |
enforce_callback_issuer |
Logical or When |
scopes |
Character vector of permissions to request. The provider defines
the available names. For OIDC ( |
resource |
Optional RFC 8707 resource indicator(s). Supply a character
vector of absolute URIs to request audience-restricted tokens for one or
more protected resources. Each value is sent as a repeated |
claims |
Optional request for specific OIDC user information, beyond scopes.
Default Lists are JSON-encoded with |
state_store |
Storage for pending logins. The default
|
state_payload_max_age |
Maximum age of a pending login's encrypted state, in seconds. Default 300. This is checked separately from the state store's entry lifetime; both must allow the returning login. |
state_entropy |
Length in characters of the random state identifier, from 22 to 128. Default 64. Most apps should keep the default. |
state_key |
Secret used to encrypt and protect pending login details.
A random key is generated when omitted. This is separate from
For multiple R processes, supply the same key and shared |
scope_validation |
Controls how scope discrepancies are handled when
the authorization server grants fewer scopes than requested. RFC 6749
Section 3.3 permits servers to issue tokens with reduced scope, and
Section 5.1 allows token responses to omit
|
claims_validation |
What to do if requested claims are missing or have
unexpected values: |
userinfo_jwt_required_time_claims |
Optional character vector of
temporal JWT claims that must be present when the UserInfo response is a
signed JWT ( Default is |
required_acr_values |
Optional character vector of acceptable login
requirements, such as a provider's multi-factor authentication (MFA) policy.
Use the provider's Authentication Context Class Reference (ACR) identifiers.
The validated ID token must contain a matching |
introspect |
If |
introspection_checks |
Optional character vector of additional
requirements to enforce on the introspection response when
|
endpoint_auth |
Named list of authentication overrides for |
authorization_server_mode |
Declares whether this client is part of an application that can interact with more than one authorization server, and which RFC 9700 mix-up defense it uses. One of:
|
authorization_server_redirect_uris |
Complete character vector of
redirect URIs used by the application for its authorization servers when
|
dpop_require_observed_cnf |
Logical. When |
jarm_signed_response_alg |
Optional expected JWS algorithm for
signed JWT Secured Authorization Responses (JARM). When omitted and the
effective response mode is JARM, shinyOAuth defaults to |
jarm_encrypted_response_alg |
Optional expected JWE
key-management algorithm for encrypted JARM responses. Current inbound
support is limited to |
jarm_encrypted_response_enc |
Optional expected JWE
content-encryption algorithm for encrypted JARM responses. Current inbound
support is limited to the AES-CBC-HMAC family ( |
jarm_decryption_private_key |
Optional private key
used to decrypt encrypted JARM responses. Can be an |
jarm_decryption_private_key_kid |
Optional key
identifier ( |
jarm_max_lifetime |
Positive number of seconds. Maximum accepted
lifetime for a JARM response JWT. Default is 600 seconds, matching JARM's
recommended 10-minute upper bound for authorization response JWTs. When a
JARM payload includes |
mtls_require_observed_cnf |
Logical, default |
trusted_id_token_audiences |
Character vector of additional ID-token
audiences explicitly trusted by this client. Defaults to |
compare_callback_issuer |
Logical or |
client_assertion_typ |
JWT header |
resource_bases |
Optional named character vector of approved API base
URLs for |
required_scopes |
Optional requested scopes that every usable connection
needs, default |
label |
Optional display label used in connection summaries; defaults to
the provider name, with control characters replaced by spaces and shortened
to 128 UTF-8 bytes if needed. If the provider name is empty, missing or not a
single string, the default is |
authorization_method |
Browser method for sending the authorization
request: |
scope_policy |
Internal versioned scope policy. Leave the default for
ordinary OAuth clients. SMART adapters install their own policy, including
required permissions; these checks cannot be disabled by |
smart |
Internal SMART configuration installed by |
introspect_elements |
Compatibility alias for |
client_private_key |
Compatibility alias for |
client_private_key_kid |
Compatibility alias for |
userinfo_jwt_required_temporal_claims |
Compatibility alias for |
mtls_request_certificate_bound_access_tokens |
Compatibility alias for |
tls_client_cert_file |
Compatibility alias for |
tls_client_key_file |
Compatibility alias for |
tls_client_key_password |
Compatibility alias for |
tls_client_ca_file |
Compatibility alias for |
authorization_request_mode |
Compatibility alias for |
authorization_request_signing_alg |
Compatibility alias for |
authorization_request_audience |
Compatibility alias for |
authorization_request_encryption_alg |
Compatibility alias for |
authorization_request_encryption_enc |
Compatibility alias for |
authorization_request_encryption_kid |
Compatibility alias for |
authorization_request_ttl |
Compatibility alias for |
authorization_request_nbf_skew |
Compatibility alias for |
Details
Configure the app registration with provider, client_id, client_secret
(if issued), redirect_uri, and scopes. Create the client outside your Shiny
server() function, then pass it to oauth_module_server().
Use the state-store settings for deployments where callbacks can reach
different R processes, and the validation settings to enforce required
scopes, claims, or authentication context. Certificate (mTLS), key-binding
(DPoP), and signed-request/response (JAR/JARM) settings enable those protocol
features when supported by your provider and required by your deployment. See
the advanced security vignette for examples. The defaults described below
refer to oauth_client() unless stated otherwise.
Value
Calling the constructor creates an OAuthClient object.
Examples
# Register an app with GitHub and store its credentials in your environment.
# This creates the configuration; it does not start login or contact GitHub.
client <- oauth_client(
provider = oauth_provider_github(),
client_id = "your-client-id",
client_secret = "your-client-secret",
redirect_uri = "http://127.0.0.1:8100",
scopes = c("read:user", "user:email")
)
# In a real app, read credentials with Sys.getenv() and create client
# outside server(). Inside server(), start login with:
# auth <- oauth_module_server("auth", client)
OAuthConnection R6 class
Description
Make API requests using a Shiny session's current OAuth credentials and the
client/API configuration supplied by an OAuthClient. For example, a hospital
connection selects that hospital's API address and reads the session's current
token for each request. Create it inside server() with oauth_connection()
or the connection(id) method of oauth_connections_server().
Use [["request"]]() to call an approved API, [["is_usable"]]()
to check local availability and [["summary"]]() for status without credentials.
Details
The existing reactive token already updates on refresh; this object
combines that lookup with client selection, API-address restrictions and
session checks. With oauth_connection(), the application supplies the
matching module's token source; the manager resolves its own stored records.
These are optional shinyOAuth conveniences, not SMART on FHIR protocol objects.
Each operation resolves the current credentials, so refresh and logout are
reflected without replacing the reference. oauth_module_server() owns the
lifecycle of ordinary references; oauth_connections_server() owns managed
references and supplies [["refresh"]](). Every reference expires when its Shiny
session closes. A manager can retain the underlying grant across redirects;
a new session obtains a new reference after verifying the local owner.
Call [["is_usable"]](), [["summary"]]() and [["request"]]() in the owning session's
reactive context. If the connection cannot be resolved, [["is_usable"]]() returns
FALSE; [["summary"]]() and [["request"]]() raise an error. The ID is read-only and
cloning is disabled.
Managed resource and status reads do not count as owner activity. Record user
actions with the manager's touch() method in an input event handler; automatic
reactive updates must not prolong an idle owner's session.
Active bindings
idRead-only opaque character string identifying this reference. A manager uses the stored grant's ID across sessions and refreshes;
oauth_connection()generates an ID lasting only for that reference. The ID is never an access token and does not authorize access by itself.
Methods
Public methods
OAuthConnection$new()
Initialize a reference. This constructor is for internal use;
applications should use oauth_connection() or the manager's
connection(id) method to establish session ownership.
Usage
OAuthConnection$new(id, client, resolve, refresh = NULL)
Arguments
idOpaque character string identifying the reference.
clientThe OAuthClient to bind to this reference.
resolveInternal function with no arguments that enforces session ownership and returns a list with
clientidentical to this reference's client andtokencontaining the current OAuthToken orNULL. It must raise an error when the owning session is unavailable.refreshOptional internal function implementing a manager's coordinated refresh. Legacy session references leave this
NULL.
Returns
A new OAuthConnection instance.
OAuthConnection$is_usable()
Check whether the current token is locally usable. This checks token
presence, known unexpired lifetime and the client's required scopes.
It does not refresh the token, contact the provider or guarantee remote
authorization. Request-specific scopes are checked by [["request"]]().
Usage
OAuthConnection$is_usable()
Returns
A single logical value: TRUE for an active or limited
connection, otherwise FALSE, including when resolution fails.
OAuthConnection$refresh()
Refresh a connection created by oauth_connections_server(). The manager
coordinates refresh and verifies ownership before updating credentials.
References created with oauth_connection() use their existing module's
refresh lifecycle and cannot invoke this method.
Usage
OAuthConnection$refresh(scopes = NULL)
Arguments
scopesOptional non-empty character vector requesting fewer permissions for this connection, or
NULL(default). Scopes must be covered by the current grant and client configuration, and retain the client's required scopes. SMART clients use semantic coverage.
Details
After explicit narrowing succeeds, subsequent refreshes (including
automatic refreshes and refreshes in another retained Shiny session)
request the accepted scope limit. Widening requires a new authorization.
This is a local connection policy: OAuth refresh-token scope itself is
not reduced by requesting a narrower access token. Ordinary OAuth
connections explicitly request their retained granted scopes when known,
including when no explicit narrowing was selected. SMART omits request
scope while its permissions equal the original launch grant.
Providers may reject requested scopes; there is no retry without them.
OIDC clients that require UserInfo must retain openid; narrowing that
removes it is rejected before exchange. Include any additional scopes
needed by the provider's profile endpoint in the client's required_scopes.
Returns
TRUE after a successful commit, or a promise resolving to TRUE
when the manager uses async transport. Failure raises a redacted error.
OAuthConnection$summary()
Resolve the current connection and return status information without credentials, identity claims or token extension fields. Raises an error when called outside the owning session or after that session closes.
Usage
OAuthConnection$summary()
Details
Managed lifecycle states take precedence: refreshing means a refresh
claim is in progress, uncertain requires a new authorization after an
ambiguous refresh outcome, disconnected means local access was removed,
and unavailable means the stored credentials could not be restored.
Otherwise token status is evaluated in this order:
-
disconnected: there is no current token. -
expiry_unknown: the token's expiry is unknown. -
expired: the token has reached its expiry time. -
insufficient_scope: the grant lacks a client-required scope. -
limited: required scopes are covered, but some other requested scopes are absent from the grant. -
active: all requested scopes are covered.
Scope checks use the token's current granted_scopes, which may be
assumed or carried forward when an ordinary OAuth provider omits scope
information. SMART clients require explicit evidence and use semantic
coverage for both connection and operation permissions.
See OAuthToken for the distinction from verified scope evidence.
Returns
A named list with the following entries:
-
connection_id: the reference's character ID. -
client_label: the client's application-defined character label. -
status: one of the character values listed in this method's details. -
expires_at: numeric seconds since the Unix epoch,NA_real_when there is no token or its expiry is unknown, orInffor a non-expiring token. -
resource_ids: character vector of the client's approved resource IDs.
OAuthConnection$identity()
Read explicitly selected OIDC identity fields from the current usable
connection. Requires openid and a cryptographically validated ID token.
This method never returns raw tokens or fetches profile data.
Usage
OAuthConnection$identity(claims = c("iss", "sub"), userinfo = character())
Arguments
claimsCharacter vector of ID-token claim names, defaulting to
c("iss", "sub"). Usecharacter()to select none.userinfoCharacter vector of previously fetched UserInfo field names, defaulting to none. UserInfo must have a
subexactly matching the validated ID token before any requested profile fields are returned.
Details
Call inside the owning session's reactive context. The result contains
sensitive identity data: select only what the application needs and keep
it out of logs and generic status displays. [["summary"]]() and printing
continue to omit identity. Ordinary OAuth connections without validated
OIDC identity cannot use this accessor.
These are the last validated identity/profile snapshots; an OAuth refresh can retain earlier ID-token claims and does not establish fresh user authentication. This accessor does not log the user into your application or establish an account-retention owner. It does not count as owner activity.
Returns
A list with id_token_claims and userinfo, each containing only
selected fields that exist. Missing fields are omitted.
OAuthConnection$request()
Resolve the current token and perform an authenticated request within a named resource base. The connection must be usable, and its current grant must cover any scopes required for this operation.
Usage
OAuthConnection$request( resource_id, path = "", query = NULL, method = "GET", required_scopes = character(), configure = NULL )
Arguments
resource_idSingle character string naming an entry in the client's
resource_bases.pathSingle character string resolved relative to the selected base directory;
""selects the base itself. Absolute and root-relative URLs, including pagination links, must remain within the same approved origin and base path. Dot segments and ambiguous encodings are rejected.queryOptional named list of query parameters, or
NULL.methodSingle HTTP method string, defaulting to
"GET".TRACEandTRACKare rejected by the resource transport.required_scopesCharacter vector of scopes required for this operation, in addition to the client's required scopes. They must have been requested by the client and be covered by the current grant.
character()adds no operation-specific scope check.configureOptional function taking an unauthenticated
httr2::request()and returning it with only body and application headers changed. Usehttr2::req_body_json(),httr2::req_body_form(),httr2::req_body_raw()andhttr2::req_headers(). Set the HTTP method withmethodabove. URL, transport policies, authentication and Host headers cannot be changed.
Details
Uses perform_resource_req() with the configured client for Bearer, DPoP
and mTLS authentication. Redirects are never followed. Transport error
messages are redacted to exclude resource paths, queries and response
bodies. Scope requirements are supplied by the application; they cannot
be inferred from an arbitrary API's HTTP method and path.
Returns
An httr2 response object. Invalid resources, unusable connections, insufficient scopes and transport failures raise errors.
OAuthConnection$smart_context()
Read interpreted context for a usable SMART connection in this session.
Usage
OAuthConnection$smart_context()
Returns
The sensitive context list documented in smart_context().
OAuthConnection$smart_resource()
Fetch the contextual Patient or validated fhirUser through the approved
FHIR base, using current read permissions. Prefer smart_patient() and
smart_fhir_user() in application code.
Usage
OAuthConnection$smart_resource(kind)
Arguments
kindEither
"patient"or"fhirUser".
Returns
An httr2 response. Missing context, scope or resource binding raises an error before an authenticated request is sent.
OAuthConnection$print()
Print the class name and session-binding description, with credentials redacted. This does not resolve the current token.
Usage
OAuthConnection$print(...)
Arguments
...Unused; accepted for compatibility with
base::print().
Returns
This reference, invisibly.
See Also
oauth_connection(), OAuthClient, perform_resource_req()
Examples
## Not run:
# Configure outside server(), using an existing provider:
client <- oauth_client(
provider, client_id = "registered-app",
redirect_uri = "https://app.example/callback", scopes = c("read", "write"),
resource_bases = c(api = "https://api.example/v1"),
required_scopes = "read"
)
server <- function(input, output, session) {
auth <- oauth_module_server("auth", client)
connection <- oauth_connection(client, shiny::reactive(auth[["token"]]))
output[["status"]] <- shiny::renderText(connection[["summary"]]()[["status"]])
records <- shiny::reactive({
shiny::req(connection[["is_usable"]]())
response <- connection[["request"]]("api", "records", required_scopes = "read")
httr2::resp_body_json(response)
})
}
## End(Not run)
OAuthProvider S7 class
Description
An OAuthProvider describes a service such as Google or GitHub: where users
sign in, where your app requests tokens, and which checks to perform.
Start with a provider helper such as oauth_provider_google() or
oauth_provider_oidc_discover(). Use oauth_provider() for manual setup;
these functions return an instance of this class with the corresponding
endpoint and validation settings.
Usage
OAuthProvider(
name = character(0),
auth_url = character(0),
token_url = character(0),
userinfo_url = NA_character_,
introspection_url = NA_character_,
revocation_url = NA_character_,
par_url = NA_character_,
par_required = FALSE,
authorization_request_front_channel_mode = "compat",
request_object_signing_alg_values_supported = character(0),
request_object_encryption_alg_values_supported = character(0),
request_object_encryption_enc_values_supported = character(0),
request_object_encryption_jwk = NULL,
signed_request_object_required = FALSE,
request_parameter_supported = NA,
request_uri_parameter_supported = NA,
request_uri_registration_required = NA,
token_endpoint_auth_signing_alg_values_supported = character(0),
dpop_signing_alg_values_supported = character(0),
authorization_response_iss_parameter_supported = FALSE,
response_modes_supported = character(0),
issuer = NA_character_,
issuer_match = "url",
use_nonce = is_valid_string(issuer) && isTRUE(infer_oidc_from_issuer),
use_pkce = TRUE,
pkce_method = "S256",
userinfo_required = FALSE,
userinfo_id_selector = function(userinfo) userinfo[["sub"]],
userinfo_id_token_match = FALSE,
userinfo_signed_jwt_required = FALSE,
id_token_required = is_valid_string(issuer) && isTRUE(infer_oidc_from_issuer),
id_token_validation = is_valid_string(issuer) && isTRUE(infer_oidc_from_issuer),
id_token_at_hash_required = FALSE,
extra_auth_params = list(),
extra_token_params = list(),
extra_token_headers = character(0),
mtls_endpoint_aliases = list(),
mtls_client_certificate_bound_access_tokens = FALSE,
token_auth_style = "header",
jwks_cache = cachem::cache_mem(max_age = 3600),
jwks_pins = character(0),
jwks_pin_mode = "any",
jwks_host_issuer_match = is_valid_string(issuer) && (isTRUE(id_token_validation) ||
isTRUE(id_token_required)),
jwks_host_allow_only = NA_character_,
id_token_allowed_algs = c("RS256", "RS384", "RS512", "ES256", "ES384", "ES512",
"Ed25519", "EdDSA"),
allowed_token_types = "Bearer",
leeway = getOption("shinyOAuth.leeway", 30),
infer_oidc_from_issuer = TRUE,
jwks_uri = NA_character_,
userinfo_allowed_algs = NULL,
allow_missing_token_type = FALSE,
jarm_signing_alg_values_supported = character(0),
jarm_encryption_alg_values_supported = character(0),
jarm_encryption_enc_values_supported = character(0),
jarm_tolerate_duplicate_top_level_iss = FALSE,
endpoint_auth_metadata = list(),
allowed_algs = NULL,
require_pushed_authorization_requests = NULL,
require_signed_request_object = NULL,
require_request_uri_registration = NULL,
tls_client_certificate_bound_access_tokens = NULL
)
Arguments
name |
Provider name (e.g., "github", "google"). Cosmetic only; used in logging and audit events |
auth_url |
URL of the provider's login and permission page. |
token_url |
URL where R exchanges the returned code for tokens. |
userinfo_url |
User info endpoint URL (optional) |
introspection_url |
Optional URL where the provider can confirm whether a token is still active (RFC 7662). |
revocation_url |
Optional URL where the app can ask the provider to invalidate a token, for example during logout (RFC 7009). |
par_url |
Optional Pushed Authorization Request (PAR) URL (RFC 9126).
When set, shinyOAuth first sends the authorization request from server to
provider and then redirects the browser with the returned |
par_required |
Logical. Whether the provider
requires authorization requests to be sent via PAR. When |
authorization_request_front_channel_mode |
Character scalar controlling
which browser-visible outer parameters shinyOAuth keeps when the actual
authorization request is carried by JAR or PAR. Use |
request_object_signing_alg_values_supported |
Optional vector of JWS
algorithms that the provider advertises for signed Request Objects (RFC
9101). This is mainly used for early validation when an OAuthClient
sends |
request_object_encryption_alg_values_supported |
Optional vector of JWE key-management algorithms that the provider advertises for encrypted Request Objects. This metadata is used for early validation when an OAuthClient enables Request Object encryption. |
request_object_encryption_enc_values_supported |
Optional vector of JWE content-encryption algorithms that the provider advertises for encrypted Request Objects. This metadata is used for early validation when an OAuthClient enables Request Object encryption. |
request_object_encryption_jwk |
Optional explicit recipient public key used to encrypt Request Objects when discovery-backed JWKS selection is not available or when you need to pin one specific encryption key. Accepts an OpenSSL public key, a PEM public-key string, a parsed JWK object, or a JWK JSON string. |
signed_request_object_required |
Logical. Whether the provider requires
signed Request Objects for authorization requests. When |
request_parameter_supported |
Logical or |
request_uri_parameter_supported |
Logical or |
request_uri_registration_required |
Logical or |
token_endpoint_auth_signing_alg_values_supported |
Optional vector of
JWS algorithms that the provider advertises for JWT-based client
authentication ( |
dpop_signing_alg_values_supported |
Optional vector of JWS algorithms
that the provider advertises for DPoP proof JWTs (RFC 9449). This
metadata is used for early validation of |
authorization_response_iss_parameter_supported |
Logical. Whether the
provider advertises RFC 9207 support for returning an |
response_modes_supported |
Optional character vector of OAuth/OIDC
|
issuer |
Optional authorization-server issuer URL. You need this for issuer validation and features such as ID-token validation. shinyOAuth uses it to verify issuer claims and locate signing keys (JWKS), typically through an OIDC discovery document. |
issuer_match |
Character scalar controlling how strictly the discovery
document's
In most cases, keep the default |
use_nonce |
Whether to tie the ID token to this login using a random nonce. Keep enabled for OIDC. The nonce is sent in the request and checked in the returned ID token. |
use_pkce |
Whether to protect the code exchange using Proof Key for Code
Exchange (PKCE). Leave enabled; public clients require it.
It sends a |
pkce_method |
PKCE code challenge method ("S256" or "plain"). "S256" is recommended. Use "plain" only if you are working with a provider that does not support "S256". |
userinfo_required |
Whether to fetch a user profile after token exchange.
The result is stored in |
userinfo_id_selector |
A function that extracts the user ID from the userinfo response. Should take a single argument (the userinfo list) and return the user ID as a string. This is used for helpers that need a provider-specific application user
identifier, such as audit fields. It does not replace OIDC subject binding:
when a validated ID token and UserInfo are both available, their actual
|
userinfo_id_token_match |
Whether fetched userinfo requires a validated ID
token for comparison. When both are available, their actual |
userinfo_signed_jwt_required |
Whether to require the user profile to arrive
as a signed JWT ( |
id_token_required |
Whether to require an ID token to be returned
during token exchange. If no ID token is returned, the token exchange
will fail. This only makes sense for OpenID Connect providers and may
require the client's scope to include Both the S7 constructor and |
id_token_validation |
Whether to perform ID token validation after token exchange.
This requires the provider to be a valid OpenID Connect provider with a configured
Both the S7 constructor and |
id_token_at_hash_required |
Whether to require the |
extra_auth_params |
Extra parameters for authorization URL |
extra_token_params |
Extra parameters for token exchange.
|
extra_token_headers |
Extra headers for back-channel token-style
requests (named character vector), applied only to token exchange and
refresh. Configure |
mtls_endpoint_aliases |
Optional named list of RFC 8705 mTLS endpoint
aliases. Names should follow the metadata keys such as |
mtls_client_certificate_bound_access_tokens |
Logical. Whether the
authorization server advertises RFC 8705 capability to issue
certificate-bound access tokens. This describes server capability; the
client still has to opt into mTLS separately. When |
token_auth_style |
How the client authenticates at the token endpoint. One of:
|
jwks_cache |
Storage for the provider's public signing keys. Defaults to
|
jwks_pins |
Optional character vector of RFC 7638 JWK thumbprints
(base64url) to pin against. If non-empty, fetched JWKS must contain keys
whose thumbprints match these values depending on |
jwks_pin_mode |
Pinning policy when |
jwks_host_issuer_match |
When TRUE, enforce that the discovery |
jwks_host_allow_only |
Optional explicit hostname that the jwks_uri must match.
When provided, jwks_uri host must equal this value (exact match). You can
pass either just the host (e.g., "www.googleapis.com") or a full URL; only
the host component will be used. If you need to include a port or an IPv6
literal, pass a full URL (e.g., |
id_token_allowed_algs |
Optional vector of allowed JWT algorithms for ID tokens.
Use to restrict acceptable |
allowed_token_types |
Character vector of acceptable OAuth token types
returned by the token endpoint (case-insensitive). Successful token
responses must include |
leeway |
Clock skew leeway (seconds) applied to ID token |
infer_oidc_from_issuer |
Whether setting |
jwks_uri |
Optional URL of the provider's public signing keys (JWKS). Normally these are located through OIDC discovery. Set this for manual key configuration, including OAuth-only JARM providers. |
userinfo_allowed_algs |
Optional signing algorithm allowlist for UserInfo
JWTs. |
allow_missing_token_type |
Logical, default |
jarm_signing_alg_values_supported |
Optional vector of JWS algorithms that the provider advertises for signed JWT Secured Authorization Responses (JARM). |
jarm_encryption_alg_values_supported |
Optional vector of JWE key-management algorithms that the provider advertises for encrypted JARM responses. |
jarm_encryption_enc_values_supported |
Optional vector of JWE content-encryption algorithms that the provider advertises for encrypted JARM responses. |
jarm_tolerate_duplicate_top_level_iss |
Logical. Whether shinyOAuth
should tolerate repeated identical top-level |
endpoint_auth_metadata |
Named list of independent |
allowed_algs |
Compatibility alias for |
require_pushed_authorization_requests |
Compatibility alias for |
require_signed_request_object |
Compatibility alias for |
require_request_uri_registration |
Compatibility alias for |
tls_client_certificate_bound_access_tokens |
Compatibility alias for |
Details
Endpoint URLs identify the provider's authorization, token, and profile services.
Provider helpers fill in these URLs and suitable defaults. A separate
oauth_client() holds your app's credentials and requested permissions.
See the usage vignette for the complete setup.
Value
Calling the constructor creates an OAuthProvider object.
Examples
# Configure generic OAuth 2.0 provider (no OIDC)
generic_provider <- oauth_provider(
name = "example",
auth_url = "https://example.com/oauth/authorize",
token_url = "https://example.com/oauth/token",
# Optional URL for fetching user info:
userinfo_url = "https://example.com/oauth/userinfo"
)
# Configure generic OIDC provider manually
# (This defaults to using nonce & ID token validation)
generic_oidc_provider <- oauth_provider_oidc(
name = "My OIDC",
base_url = "https://my-issuer.example.com"
)
# Configure a OIDC provider via OIDC discovery
# (requires network access)
if (interactive()) {
# Using Auth0 sample issuer as an example
oidc_discovery_provider <- oauth_provider_oidc_discover(
issuer = "https://samples.auth0.com"
)
}
# GitHub preconfigured provider
github_provider <- oauth_provider_github()
# Google preconfigured provider
google_provider <- oauth_provider_google()
# Microsoft preconfigured provider
# For a complete app using a custom tenant ID, see:
# https://lukakoning.github.io/shinyOAuth/reference/oauth_provider_microsoft.html
# Spotify preconfigured provider
spotify_provider <- oauth_provider_spotify()
# Slack via OIDC discovery
# (requires network access)
if (interactive()) {
slack_provider <- oauth_provider_slack()
}
# Keycloak
# (requires configured Keycloak realm; example below is therefore not run)
if (interactive()) {
options(shinyOAuth.allow_insecure_oidc_loopback = TRUE)
oauth_provider_keycloak(base_url = "http://localhost:8080", realm = "myrealm")
}
# Auth0
# (requires configured Auth0 domain; example below is therefore not run)
if (interactive()) {
oauth_provider_auth0(domain = "your-tenant.auth0.com")
}
# Okta
# (requires configured Okta domain; example below is therefore not run)
if (interactive()) {
oauth_provider_okta(domain = "dev-123456.okta.com")
}
OAuthToken S7 class
Description
An OAuthToken holds credentials and user information returned after login.
The Shiny module supplies it as auth[["token"]], and handle_callback() returns
it for custom integrations. Pass it to perform_resource_req() to call an
API, or to the token helpers for refresh, introspection, and revocation.
Read properties with @, for example auth[["token"]]@userinfo. Profile fields
depend on the provider. Keep access and refresh tokens out of the UI and logs.
Usage
OAuthToken(
access_token = character(0),
token_type = NA_character_,
refresh_token = NA_character_,
id_token = NA_character_,
expires_at = Inf,
userinfo = list(),
cnf = list(),
granted_scopes = character(0),
granted_scopes_verified = FALSE,
id_token_validated = FALSE,
original_id_token = NA_character_,
extra_fields = list(),
initial_extra_fields = list(),
smart_context = list(),
original_granted_scopes = character(0)
)
Arguments
access_token |
Access token |
token_type |
OAuth access token type (for example |
refresh_token |
Refresh token (if provided by the provider) |
id_token |
ID token (if provided by the provider; OpenID Connect) |
expires_at |
Numeric timestamp (seconds since epoch) when the access
token expires, |
userinfo |
List containing user information fetched from the provider's userinfo endpoint (if fetched) |
cnf |
Optional confirmation claim set returned alongside a
sender-constrained access token or observed on another token surface. For
RFC 8705 certificate-bound tokens, this may contain |
granted_scopes |
Normalized scope tokens currently associated with the
access token. When a provider omits |
granted_scopes_verified |
Logical flag indicating whether the current
token response explicitly proved |
id_token_validated |
Logical flag indicating whether the ID token was
cryptographically validated (signature verified and standard claims checked)
during the OAuth flow. Defaults to |
original_id_token |
Initial login ID token retained as the refresh
continuity baseline. Refresh never replaces it with a newer ID token.
For manually constructed tokens, the first refresh initializes this from
|
extra_fields |
List of additional parameters from the latest successful
token endpoint response. Excludes |
initial_extra_fields |
List of additional parameters from the initial
successful authorization-code exchange. Preserved across refreshes and
replaced on a new login. Defaults to an empty list for manually constructed
tokens; refresh does not infer an initial response from |
smart_context |
Internal interpreted SMART context. Empty for ordinary
tokens; populated only by SMART token processing. Use |
original_granted_scopes |
Initial accepted SMART grant, preserved across refreshes to distinguish unchanged grants from strict scope reductions. Empty for ordinary OAuth tokens. Set by SMART token processing. |
Details
The id_token_claims property is a read-only computed property that returns
the decoded JWT payload of the ID token as a named list. This surfaces all
standard and optional OIDC claims (e.g., sub, iss, aud, acr, amr,
auth_time, nonce, at_hash, etc.) without requiring manual JWT
decoding. Returns an empty list when no ID token is present or if the token
cannot be decoded.
Note: id_token_claims always decodes the JWT payload regardless
of whether the ID token's signature was verified.
Check the id_token_validated property to determine whether the claims
were cryptographically validated.
For validated Apple ID tokens, exact "true"/"false" strings in
email_verified are returned as logical values, as during validation.
The original signed id_token is retained unchanged.
Additional response parameters retain their parsed names and values,
including nested lists and explicit JSON null values (R NULL). Use
"custom_field" %in% names(token@extra_fields) to distinguish an absent field
from a field explicitly returned as null. These parameters are not ID
token claims and are not covered by id_token_validated. The initial
snapshot records the initial response data, not current access permissions.
No automatic merging, resource fetching, or interpretation is performed.
Both lists can contain sensitive data; keep them out of the UI and logs.
Value
Calling the constructor creates an OAuthToken object.
Examples
# Inside reactive server code, after a successful login:
# auth[["token"]]@userinfo
# auth[["token"]]@expires_at
# auth[["token"]]@id_token_validated
# auth[["token"]]@id_token_claims[["sub"]]
# auth[["token"]]@extra_fields[["custom_field"]]
# auth[["token"]]@initial_extra_fields[["custom_field"]]
Assess an OAuth configuration against a pinned OAuth 2.1 draft
Description
This function inspects a configured OAuthClient and its OAuthProvider for
compliance with the OAuth 2.1 draft 16 (ruleset 1.1.0) specification. It
reports configuration gaps, unresolved external prerequisites, and recommendations
without changing the configuration or making requests.
Usage
check_oauth21(config, context = list(), draft = "draft-ietf-oauth-v2-1-16")
Arguments
config |
An OAuthClient or OAuthProvider. Provider-only assessments are partial and cannot establish missing client settings. |
context |
Optional named list with |
draft |
Implemented target revision. Currently only
|
Details
Ruleset 1.1.0 covers code/refresh, enabled PAR, required UserInfo and
introspection, and the additional operations selected in context. Signing
and encryption key retrieval is included when applicable. Future resource URLs, arbitrary
request customization, browser/proxy TLS, registered redirect matching, secret
custody, and authorization/resource server behavior require separate evidence.
Optional DPoP, mTLS, PAR, JAR and JARM are not required as a bundle.
configuration_compliant is FALSE if an applicable mandatory configuration
check fails; otherwise NA if a mandatory configuration check is unresolved;
otherwise TRUE when a nonempty set of applicable mandatory checks passes.
Recommendations and external unknowns do not change that verdict. In
particular, the legacy assertion type JWT is a recommendation finding;
the assertion audience is a separate mandatory check for JWT authentication.
requirement_source distinguishes OAuth core, OIDC, extension specifications,
security guidance and local package/application policy. Callback capacity
thresholds are package recommendations, not draft-defined numeric minima;
complete encoded requests still need deployment testing. OAuth 2.1 assessment
is opt-in and does not change existing OAuth 2.0 configuration or requests.
A positive verdict applies only to the recorded scope and ruleset, with the current configuration, options, runtime and declared context. It is not certification or a test of a live deployment. Rerun after policy changes. Reports contain no client/provider objects, credentials, keys or endpoint URLs. No caches or state stores are read or changed; their method contracts are inspected without invoking them.
Value
A shinyOAuth_oauth21_assessment list with configuration_compliant,
checks, draft, ruleset_version, package_version, assessed_at,
assessment_scope, and operations. checks is a data frame with stable
id, scope, status (pass, fail, unknown, not_applicable),
requirement (MUST, SHOULD, info), message, remediation, reference,
evidence_source, requirement_source, and logical affects_verdict columns. Only rows with
affects_verdict = TRUE enter aggregation; unknown external obligations
remain visible separately.
References
https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-16
https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rfc7523bis-11
https://www.rfc-editor.org/rfc/rfc9207.html
Examples
provider <- oauth_provider(
name = "Example",
auth_url = "https://auth.example/authorize",
token_url = "https://auth.example/token",
token_auth_style = "public", use_pkce = TRUE, pkce_method = "S256"
)
client <- oauth_client(
provider, client_id = "example-client",
redirect_uri = "https://app.example/callback"
)
assessment <- check_oauth21(client)
assessment[["checks"]][assessment[["checks"]][["status"]] != "pass", ]
Alias for resource_req()
Description
Deprecated alias for resource_req().
Use resource_req() for Bearer, DPoP, and mTLS-protected resource requests instead.
Usage
client_bearer_req(
token,
url,
method = "GET",
headers = NULL,
query = NULL,
follow_redirect = FALSE,
check_url = TRUE,
client = NULL,
token_type = NULL,
dpop_nonce = NULL,
resource_hosts = NULL,
oauth_client = NULL
)
Arguments
token |
Either an OAuthToken object or a raw access token string. |
url |
The absolute URL to call. |
method |
Optional HTTP method (character). Defaults to "GET". When
the effective token type is |
headers |
Optional named list or named character vector of extra
headers to set on the request. Header names are case-insensitive.
Any user-supplied |
query |
Optional named list of query parameters to append to the URL. |
follow_redirect |
Logical or |
check_url |
Logical. If |
client |
Optional OAuthClient. Required when the effective
token type is |
token_type |
Optional override for the access token type when |
dpop_nonce |
Optional DPoP nonce to embed in the proof for this
request. This is primarily useful after a resource server challenges with
|
resource_hosts |
Optional non-empty character vector of trusted resource
host patterns, using |
oauth_client |
Compatibility alias for |
Value
Same value as resource_req().
Create a custom state store or signing-key cache
Description
Connect shinyOAuth to a shared storage backend, such as Redis or a database,
by wrapping your R functions in a cachem-like interface. Use the result as
state_store in oauth_client() to share pending login state, or as
jwks_cache in oauth_provider() to share provider signing keys (JWKS).
A shared state store is needed when a login can start on one R process and its callback can arrive at another, for example in a multi-worker deployment without sticky routing. A shared signing-key cache lets workers reuse keys fetched from the provider rather than maintaining separate caches.
Usage
custom_cache(get, set, remove, take = NULL, info = NULL, set_if_absent = NULL)
Arguments
get |
A function(key, missing = NULL) -> value. Required.
Should return the stored value, or the |
set |
A function(key, value) -> invisible(NULL). Required. Should store the value under the given key. |
remove |
A function(key) -> any. Required. Deletes the entry for |
take |
A function(key, missing = NULL) -> value. Optional. An atomic get-and-delete operation. When provided, shinyOAuth uses
Should return the stored value and atomically remove the entry, or
return the If your backend supports atomic get-and-delete natively
(e.g., Redis When |
info |
Function() -> list(max_age = seconds, ...). Optional TTL information from |
set_if_absent |
A function(key, value, ttl = NULL) -> logical. Optional. An atomic set-if-missing operation for shared JWKS caches and callback
bridge slots. It must store
|
Details
This helper adapts your storage functions; it does not create a database, open connections, or make process-local storage shared. Your backend must preserve stored R values and expire entries after their configured lifetime.
Value
An R6 object exposing cachem-like [["get"]]/[["set"]]/[["remove"]]/[["info"]] methods
and the optional [["take"]] and [["set_if_absent"]] atomic methods.
Shared login state in multi-worker deployments
The default cachem::cache_mem() state store belongs to one R process.
If a load balancer sends the returning callback to another worker, that
worker cannot find the pending login and validation fails. Configure all
workers with access to the same external store when routing does not keep
the authorization request and callback on the same process.
For a shared state_store, implement take: it must read and delete a pending
login as one indivisible operation, so two requests cannot use it. Redis
GETDEL and SQL DELETE ... RETURNING are examples of backend operations
that can do this. Use the same state_key and matching provider/client
settings on every worker. Separate reads and deletes, including those in
cachem::cache_disk(), cannot ensure single-use state under concurrent access.
See the deployment guidance.
Store values are small R lists; preserve them without interpreting fields.
Pending login records in external stores are AES-GCM sealed with a distinct
key derived from state_key and bound to the client, provider, and state key.
The backend receives an opaque sealed_state_record string instead of the
browser binding, PKCE verifier, and nonce. Existing unsealed external records
are rejected; users with logins pending across an upgrade must restart login.
The default process-local memory store keeps records within the R trust boundary.
Encryption does not replace backend access controls, expiry, or atomic take:
a backend able to restore consumed entries can still violate single-use state.
With request_object_mode = "request_uri", hosted Request Objects are stored
as separate records containing the signed or encrypted JWT and its expiry.
These records do not use the pending-login sealed_state_record wrapper.
A signed, unencrypted JWT has readable claims, including authorization request
details. Configure Request Object JWE encryption when those claims need
confidentiality, and apply backend access controls and expiry to these records.
For a state store, returning max_age in seconds from info() also lets
oauth_module_server() align the browser cookie lifetime with the store.
Reporting this value does not expire entries; your backend must enforce it.
Shared provider signing keys
A shared jwks_cache can reduce repeated key downloads when several R
workers use the same provider. This is independent of sharing login state:
sharing signing keys alone does not let another worker resume a login.
Key caching uses get and set. Also implement set_if_absent to coordinate
rate-limited forced key refreshes across workers, for example when the provider
rotates its signing keys. Without that atomic operation, forced refresh is
disabled for custom/shared caches. Use separate stores or key namespaces for
login state and signing keys when they require different expiry policies.
Examples
# This in-memory example illustrates the cache interface in one R process.
# It does not share entries across workers or implement timed expiry.
# A production shared store must implement both itself.
mem <- new.env(parent = emptyenv())
my_cache <- custom_cache(
get = function(key, missing = NULL) {
base::get0(key, envir = mem, ifnotfound = missing, inherits = FALSE)
},
set = function(key, value) {
assign(key, value, envir = mem)
invisible(NULL)
},
remove = function(key) {
if (exists(key, envir = mem, inherits = FALSE)) {
rm(list = key, envir = mem)
}
invisible(NULL)
},
# In a shared store, replace this with the backend's atomic get-and-delete
# operation, such as Redis GETDEL. This R environment is process-local.
take = function(key, missing = NULL) {
val <- base::get0(key, envir = mem, ifnotfound = missing, inherits = FALSE)
if (exists(key, envir = mem, inherits = FALSE)) {
rm(list = key, envir = mem)
}
val
},
info = function() list(max_age = Inf)
)
Check selected debugging options (deprecated)
Description
Deprecated helper that errors when a small subset of shinyOAuth's options that relax security checks or expose debugging details are enabled. Use explicit startup checks for the exact options your deployment permits or forbids instead.
Usage
error_on_softened()
Details
It only checks the following options:
-
shinyOAuth.skip_browser_token: Skips browser cookie presence check -
shinyOAuth.skip_id_sig: Skips ID token signature verification -
shinyOAuth.expose_error_body: Exposes HTTP response bodies and claim values in diagnostics -
shinyOAuth.allow_unsigned_userinfo_jwt: Accepts unsigned (alg=none) UserInfo JWTs -
shinyOAuth.allow_redirect: Allows sensitive HTTP flows to follow redirects
Value
Invisible TRUE if none of those options are enabled; otherwise an
error is thrown.
Examples
# Note: error_on_softened() is deprecated because it only checks a narrow subset
# of shinyOAuth's security-relaxing options
# Throw an error if one of the options listed in ?error_on_softened is enabled.
# Below call does not error if run with default options:
error_on_softened()
# Below call would error (is therefore not run):
if (interactive()) {
options(shinyOAuth.skip_id_sig = TRUE)
error_on_softened()
}
Fetch a user's profile (UserInfo)
Description
Retrieve profile information using the user's access token. Call this when
fetching a profile on demand, reloading profile fields, or managing tokens
outside the Shiny module. It returns the provider's profile as an R list.
The Shiny module fetches and stores this information during login when
userinfo_required = TRUE; that result is available as auth[["token"]]@userinfo.
Usage
get_userinfo(
client,
token,
token_type = NULL,
shiny_session = NULL,
oauth_client = NULL
)
Arguments
client |
OAuthClient object. The client must have a
|
token |
Either an OAuthToken object or a raw access token string. |
token_type |
Optional override for the access token type when |
shiny_session |
Optional captured Shiny session details for audit events.
Normally supplied by the module; leave |
oauth_client |
Compatibility alias for |
Details
The provider must have a userinfo_url: the OpenID Connect (OIDC) UserInfo
endpoint, or an OAuth provider's profile API. With OIDC, this function checks that
userinfo belongs to the same user as a validated ID token when available.
If provider policy requires that comparison, an absent validated ID token
causes an error. Prefer passing the complete OAuthToken, which carries
the ID token needed for this check.
Ordinary JSON profiles and signed JWT UserInfo responses are supported.
Signed responses are verified using the provider's signing keys and
userinfo_allowed_algs; encrypted UserInfo is not supported. Set
userinfo_signed_jwt_required on the provider to require a signed response,
and userinfo_jwt_required_time_claims on the client to require time claims
such as exp. Present time claims are checked even when not required.
For certificate-bound tokens (mTLS) and key-bound tokens (DPoP), the helper uses the client's certificate or signing key and checks the token binding. It handles one DPoP nonce challenge with a fresh-proof retry. See the advanced security vignette for configuration.
Value
A list containing the user information returned by the provider. For JSON responses, arrays are simplified to vectors or data frames where possible. Signed JWT responses retain arrays as lists.
Examples
# get_userinfo(), introspect_token(), and refresh_token() are typically
# called by oauth_module_server() according to your provider/client and
# module settings, rather than directly by application code. The module
# also calls revoke_token() during logout when the provider supports it.
# These helpers are exported for custom login flows, on-demand profile or
# token checks, and applications that manage token lifetime themselves.
#
# The examples below require a real token from a completed login.
# Inside a reactive expression in server(), after creating auth with
# oauth_module_server() and confirming auth[["authenticated"]]:
if (interactive()) {
token <- auth[["token"]]
user_info <- get_userinfo(client, token)
# Requires an introspection endpoint. NA means activity is unknown.
result <- introspect_token(client, token)
isTRUE(result[["active"]])
# Requires a refresh token. Keep the returned replacement.
token <- refresh_token(client, token)
# Requires a revocation endpoint to invalidate the token at the provider.
result <- revoke_token(client, token, token_kind = "refresh")
}
Handle OAuth 2.0 callback: verify state, swap code for token, verify token
Description
Check a returning login request and exchange the provider's temporary
authorization code for an OAuthToken (OAuth 2.0 Authorization Code flow).
Use this in a custom callback handler after starting authorization with
prepare_call(). It applies shinyOAuth's state, token, and configured
identity checks while your application manages the HTTP callback and stores
the returned token. oauth_module_server() handles these responsibilities
for Shiny sessions.
Usage
handle_callback(
client,
code,
state,
browser_token,
shiny_session = NULL,
iss = NULL,
oauth_client = NULL,
payload = NULL
)
Arguments
client |
An OAuthClient object. |
code |
Authorization code received from the provider on a classic direct callback. |
state |
Encrypted state payload returned by the provider on a classic
direct callback. This should be the same value that was originally sent in
|
browser_token |
Browser token present in the user's session. This is
usually managed by |
shiny_session |
Optional captured Shiny session details for audit events.
Normally supplied by the module; leave |
iss |
Optional RFC 9207 callback issuer ( This low-level API cannot verify which redirect URI received the response.
Clients configured with |
oauth_client |
Compatibility alias for |
payload |
Compatibility alias for |
Details
Pass the returned code, the callback's state, and the
browser token saved for this login. This helper accepts direct code/state
callbacks only. For signed responses using JWT Secured Authorization
Response Mode (JARM; "jwt", "query.jwt", or
"form_post.jwt"), use oauth_module_server() and, for POST responses,
oauth_form_post_ui(). There is no public JARM resume API.
Value
An OAuthToken object. If callback validation, token exchange, or token verification fails, the function raises an error.
Examples
# Advanced example: your code supplies browser redirects and callback handling.
# For a Shiny app, oauth_module_server() manages these steps for you.
if (interactive()) {
# Define client
client <- oauth_client(
provider = oauth_provider_github(),
client_id = Sys.getenv("GITHUB_OAUTH_CLIENT_ID"),
client_secret = Sys.getenv("GITHUB_OAUTH_CLIENT_SECRET"),
redirect_uri = "http://127.0.0.1:8100"
)
# Get the login URL and store state in client's state store
# `<browser_token>` must be unpredictable and persisted for this transaction
# in storage bound to the application's exact origin (scheme, host, port).
# The module combines origin-scoped storage with an independent marker cookie
# and checks both on return. A cookie alone does not provide this boundary:
# cookies can be shared by applications on different ports of the same host.
# Shiny applications should use oauth_module_server() for the complete flow.
authorization_url <- prepare_call(client, "<browser_token>")
# Redirect user to authorization URL; retrieve code & state from the query;
# recover this transaction's `<browser_token>` through the origin-bound flow
# and verify its independent marker before calling handle_callback().
code <- "..."
state <- "..."
browser_token <- "..."
# Handle callback, exchanging code for token and validating state
token <- handle_callback(client, code, state, browser_token)
}
Introspect an OAuth 2.0 token
Description
Ask the provider to check an access or refresh token. This is called token
introspection and requires a configured introspection_url. Use it when you
need the provider's current token status rather than only a locally recorded
expiry time: a token may have been revoked before its expiry. To require it
automatically during login and refresh, set introspect = TRUE on
oauth_client().
Usage
introspect_token(
client,
token,
token_kind = c("access", "refresh"),
async = FALSE,
shiny_session = NULL,
oauth_client = NULL,
oauth_token = NULL,
which = NULL
)
Arguments
client |
OAuthClient object |
token |
OAuthToken object to introspect |
token_kind |
Which token to introspect: "access" (default) or "refresh". |
async |
If |
shiny_session |
Optional captured Shiny session details for audit events.
Normally supplied by the module; leave |
oauth_client |
Compatibility alias for |
oauth_token |
Compatibility alias for |
which |
Compatibility alias for |
Details
Read result[["active"]]: TRUE means active, FALSE means inactive, and NA
means the result is unknown. Use isTRUE(result[["active"]]) if your code must
require a definite confirmation.
Unsupported endpoints, missing tokens, unsuccessful HTTP responses, and
unusable response bodies return a descriptive status. The provider must
return active as a JSON boolean. Other types return "invalid_active".
Encoded or decoded body limits return "body_too_large"; unsupported
compression returns "unsupported_encoding". Both leave active = NA.
Other transport failures and decoding errors raise conditions (or reject
the asynchronous promise) instead of returning a status result.
Requests use the client's configured credentials and token_auth_style.
Value
A list with fields:
-
supported: logical,TRUEwhen an introspection endpoint is configured. -
active: logical orNA, whereNAmeans the provider did not return a usable RFC 7662activevalue. -
raw: parsed introspection response list, orNULLwhen the endpoint is unsupported or the response could not be parsed. -
status: machine-readable status such as"ok","introspection_unsupported","missing_token","invalid_json","missing_active","invalid_active", or"http_<code>".
Examples
# get_userinfo(), introspect_token(), and refresh_token() are typically
# called by oauth_module_server() according to your provider/client and
# module settings, rather than directly by application code. The module
# also calls revoke_token() during logout when the provider supports it.
# These helpers are exported for custom login flows, on-demand profile or
# token checks, and applications that manage token lifetime themselves.
#
# The examples below require a real token from a completed login.
# Inside a reactive expression in server(), after creating auth with
# oauth_module_server() and confirming auth[["authenticated"]]:
if (interactive()) {
token <- auth[["token"]]
user_info <- get_userinfo(client, token)
# Requires an introspection endpoint. NA means activity is unknown.
result <- introspect_token(client, token)
isTRUE(result[["active"]])
# Requires a refresh token. Keep the returned replacement.
token <- refresh_token(client, token)
# Requires a revocation endpoint to invalidate the token at the provider.
result <- revoke_token(client, token, token_kind = "refresh")
}
Check a URL against the package's host policy
Description
Test whether a URL is allowed by shinyOAuth's ordinary scheme and host rules.
HTTPS is accepted by default. HTTP is limited to local development hosts
unless you change allowed_non_https_hosts. Supply allowed_hosts to
restrict which services your app may contact.
Call this when checking configured endpoint URLs or diagnosing a URL-policy rejection. The provider and API request helpers apply these checks internally; a direct call lets you inspect the result without making a network request.
Usage
is_ok_host(
url,
allowed_non_https_hosts = getOption("shinyOAuth.allowed_non_https_hosts", default =
c("localhost", "127.0.0.1", "::1", "[::1]")),
allowed_hosts = getOption("shinyOAuth.allowed_hosts", default = NULL)
)
Arguments
url |
Single URL or vector of URLs (character; length 1 or more) |
allowed_non_https_hosts |
Character vector of hostnames that are allowed to use HTTP instead of HTTPS. Defaults to localhost equivalents. Supports globs |
allowed_hosts |
Optional allowlist of hosts/domains; if supplied (length > 0), only these hosts are permitted. Supports globs |
Details
Both host lists support * (any characters), ? (one character), and a
leading dot: ".example.com" matches the domain and its subdomains.
"*" permits every host. If allowed_hosts is empty, only the scheme
rules apply. Missing values, empty strings, and malformed URLs return FALSE.
If the scheme is absent, this helper tries HTTP, then HTTPS. Request helpers
can impose additional requirements, including an absolute URL. OIDC
discovery has a separate HTTPS policy and requires an explicit loopback
development opt-in; a TRUE result here does not override it.
Defaults come from shinyOAuth.allowed_hosts and
shinyOAuth.allowed_non_https_hosts.
Value
Logical indicator (TRUE if all URLs pass all checks; FALSE otherwise)
Examples
# HTTPS allowed by default
is_ok_host("https://example.com")
# HTTP allowed for localhost
is_ok_host("http://localhost:8100")
# Restrict to a specific domain (allowlist)
is_ok_host("https://api.example.com", allowed_hosts = c(".example.com"))
# Caution: a catch-all pattern disables host restrictions
# (only scheme rules remain). Avoid unless you truly intend it
is_ok_host("https://anywhere.example", allowed_hosts = c("*"))
Configure ownership of retained OAuth connections
Description
Choose who may use saved OAuth connections: the browser that created them,
or an account already authenticated by your application. Supply the policy to
oauth_connections() with the matching retention mode. These factories do not
set cookies, authenticate users or enable retention on oauth_module_server()
calls by themselves.
Usage
oauth_browser_owner(
idle_timeout = 1800,
absolute_timeout = 28800,
same_site = c("Lax", "Strict"),
allow_http_loopback = FALSE,
max_entries = 1000L
)
oauth_account_owner(
resolver,
idle_timeout,
absolute_timeout,
reauth_after_seconds,
max_entries = 1000L
)
## S3 method for class 'OAuthOwnerPolicy'
print(x, ...)
Arguments
idle_timeout |
Maximum owner inactivity in seconds. Resource and status
reads do not count as activity. Use the server manager's |
absolute_timeout |
Maximum owner lifetime in seconds, independent of
activity. Must be at least |
same_site |
Owner-cookie policy, |
allow_http_loopback |
Explicit development-only exception for HTTP on
localhost or a loopback address. Default |
max_entries |
Maximum owner-registry entries per manager, a positive
whole number. Browser mode counts browsers that authorize a service. A
separate provisional visitor pool has the same limit; its oldest entries
may be replaced and expire after at most five minutes, unless a live
authorization transaction protects them until its expiry. When all visitor
entries have pending authorizations, new visitors are rejected. Account mode counts
authentication generations, including retired generations until their local
reauthentication deadline. This limit is independent of the connection
store's |
resolver |
Trusted application function accepting the current Shiny
session. On every call it must validate the application's local login and
return |
reauth_after_seconds |
Maximum age of the verified local authentication, in seconds. Required for account retention; refresh cannot reset this age. |
x |
An |
... |
Unused print arguments. |
Details
Browser retention identifies an authorized browser session, not a verified
person. The owner cookie is HttpOnly, host-only, has root path and uses Secure
and a __Host- name on HTTPS. It contains no token or patient data. Server-side
idle and absolute limits are authoritative. A cookie is never accepted as an
owner without a matching live server record for this application origin.
Cookie rotation invalidates the previous session generation immediately and preserves the original absolute lifetime. Local logout removes the live owner session. The manager checks that generation before code exchange and credential commit, with no grace period for pending authorization, and handles credential cleanup after logout. An external provider login cannot establish a local owner or implicitly link browser connections to an account.
A full owner registry rejects new retained owners without evicting live
sessions or retirement records. Browser session end does not release ownership; logout or
idle/absolute expiry does. Account logout retains its retired generation until
authenticated_at + reauth_after_seconds so it cannot be enrolled again.
Size max_entries for that entire window, not only simultaneous Shiny sessions.
Account retention requires finite idle, absolute and local reauthentication
lifetimes. The internal session registry re-runs resolver when resolving or
validating an owner and checks the intended subject/session generation.
Expiry or logout retires that generation until a fresh local authentication
session is supplied. A resolver error fails closed with a redacted error.
The application remains responsible for validating its local session, including
signature, expiry, revocation and account changes. Supplying this configuration
is not proof that an arbitrary user ID is authenticated.
Value
An OAuthOwnerPolicy configuration object. Browser policies use an
opaque server-issued cookie and server-side owner registry. Account policies
use the trusted local-session resolver described below.
See Also
oauth_connection_store_memory()
Examples
# Browser connections expire after 15 minutes idle or 8 hours in total.
oauth_browser_owner(idle_timeout = 15 * 60, absolute_timeout = 8 * 3600)
# Supply your application's trusted local-login validator for account retention.
# It must revalidate the session on each call and return the documented fields.
account_policy <- function(validate_local_session) {
oauth_account_owner(
resolver = validate_local_session,
idle_timeout = 15 * 60,
absolute_timeout = 8 * 3600,
reauth_after_seconds = 8 * 3600
)
}
Configure OAuth/OIDC client credentials and login settings
Description
Create a client with the credentials assigned by your provider, the URL where
users return after login, and the permissions your app needs. Pass the result
to oauth_module_server().
Usage
oauth_client(
provider,
client_id,
client_secret = character(0),
redirect_uri,
enforce_callback_issuer = NULL,
scopes = character(0),
resource = character(0),
claims = NULL,
state_store = cachem::cache_mem(max_age = 300),
state_payload_max_age = 300,
state_entropy = 64,
state_key = random_urlsafe(128),
client_assertion_private_key = NULL,
client_assertion_private_key_kid = NULL,
client_assertion_alg = NULL,
client_assertion_audience = NULL,
mtls_client_cert_file = NULL,
mtls_client_key_file = NULL,
mtls_client_key_password = NULL,
mtls_client_ca_file = NULL,
mtls_certificate_bound_access_tokens = FALSE,
request_object_mode = c("parameters", "request", "request_uri"),
response_mode = NULL,
request_object_signing_alg = NULL,
request_object_audience = NULL,
request_object_encryption_alg = NULL,
request_object_encryption_enc = NULL,
request_object_encryption_kid = NULL,
request_object_ttl = 45,
request_object_nbf_skew = NULL,
dpop_private_key = NULL,
dpop_private_key_kid = NULL,
dpop_signing_alg = NULL,
dpop_require_access_token = NULL,
scope_validation = c("warn", "strict", "none"),
claims_validation = c("none", "warn", "strict"),
userinfo_jwt_required_time_claims = character(0),
required_acr_values = character(0),
introspect = FALSE,
introspection_checks = character(0),
authorization_server_mode = c("single", "multi_issuer", "multi_redirect_uri"),
authorization_server_redirect_uris = character(0),
dpop_require_observed_cnf = FALSE,
jarm_signed_response_alg = NULL,
jarm_encrypted_response_alg = NULL,
jarm_encrypted_response_enc = NULL,
jarm_decryption_private_key = NULL,
jarm_decryption_private_key_kid = NULL,
jarm_max_lifetime = 600,
endpoint_auth = list(),
mtls_require_observed_cnf = TRUE,
trusted_id_token_audiences = character(0),
compare_callback_issuer = NULL,
client_assertion_typ = "JWT",
authorization_method = "GET",
resource_bases = character(),
required_scopes = character(),
label = default_client_label(provider),
...,
introspect_elements = NULL
)
Arguments
provider |
The service configuration, created with a provider helper such as
|
client_id |
The identifier assigned when you register your app with the provider. |
client_secret |
The secret issued for your app, preferably read with
It is required for |
redirect_uri |
The URL where users return after login. It must match the callback URL registered with your provider, including scheme, host, port, and path. Use HTTPS in production. |
enforce_callback_issuer |
Logical or When |
scopes |
Character vector of permissions to request. The provider defines
the available names. For OIDC ( |
resource |
Optional RFC 8707 resource indicator(s). Supply a character
vector of absolute URIs to request audience-restricted tokens for one or
more protected resources. Each value is sent as a repeated |
claims |
Optional request for specific OIDC user information, beyond scopes.
Default Lists are JSON-encoded with |
state_store |
Storage for pending logins. The default
|
state_payload_max_age |
Maximum age of a pending login's encrypted state, in seconds. Default 300. This is checked separately from the state store's entry lifetime; both must allow the returning login. |
state_entropy |
Length in characters of the random state identifier, from 22 to 128. Default 64. Most apps should keep the default. |
state_key |
Secret used to encrypt and protect pending login details.
A random key is generated when omitted. This is separate from
For multiple R processes, supply the same key and shared |
client_assertion_private_key |
Optional private key for |
client_assertion_private_key_kid |
Optional key identifier (kid) to include in the JWT header
for |
client_assertion_alg |
Optional JWT signing algorithm to use for client assertions.
When omitted, defaults to |
client_assertion_audience |
Optional override for the |
mtls_client_cert_file |
Optional path to the PEM-encoded client
certificate (or certificate chain) used for RFC 8705 mutual TLS (mTLS) client
authentication and certificate-bound protected-resource requests. Required
when |
mtls_client_key_file |
Optional path to the PEM-encoded private key used
with |
mtls_client_key_password |
Optional password used to decrypt an encrypted
PEM private key referenced by |
mtls_client_ca_file |
Optional path to a PEM CA bundle used to validate the remote HTTPS server certificate when making mTLS requests. This is mainly useful for local or test environments that use self-signed server certificates. |
mtls_certificate_bound_access_tokens |
Logical. Whether this
client intends to request RFC 8705 certificate-bound access tokens when
the provider advertises that capability. Default is Set this to Requires |
request_object_mode |
Controls how the authorization request is transported to the provider.
If the provider has a Use a signed Request Object when the provider requires JAR or when it must
verify the integrity of the authorization parameters. |
response_mode |
How the provider returns the login result. Leave Signed responses (JWT Secured Authorization Response Mode, JARM)
use |
request_object_signing_alg |
Optional JWS algorithm override for
signed authorization requests when |
request_object_audience |
Optional override for the |
request_object_encryption_alg |
Optional JWE key-management
algorithm override for encrypted Request Objects. Current outbound support
is limited to |
request_object_encryption_enc |
Optional JWE content-encryption
algorithm override for encrypted Request Objects. Current outbound support
is limited to the AES-CBC-HMAC family ( |
request_object_encryption_kid |
Optional key identifier ( |
request_object_ttl |
Positive number of seconds to keep signed
authorization request objects ( |
request_object_nbf_skew |
Optional non-negative number of
seconds. When provided, shinyOAuth adds an |
dpop_private_key |
Private key for tying tokens to this app's requests
using Demonstrating Proof of Possession (DPoP). Only needed when your
provider/API supports DPoP. Accepts an
|
dpop_private_key_kid |
Optional key identifier ( |
dpop_signing_alg |
Optional JWT signing algorithm to use for DPoP
proofs. When omitted, a compatible asymmetric default is selected based on
the private key type/curve (for example |
dpop_require_access_token |
Logical or |
scope_validation |
Controls how scope discrepancies are handled when
the authorization server grants fewer scopes than requested. RFC 6749
Section 3.3 permits servers to issue tokens with reduced scope, and
Section 5.1 allows token responses to omit
|
claims_validation |
What to do if requested claims are missing or have
unexpected values: |
userinfo_jwt_required_time_claims |
Optional character vector of
temporal JWT claims that must be present when the UserInfo response is a
signed JWT ( Default is |
required_acr_values |
Optional character vector of acceptable login
requirements, such as a provider's multi-factor authentication (MFA) policy.
Use the provider's Authentication Context Class Reference (ACR) identifiers.
The validated ID token must contain a matching |
introspect |
If |
introspection_checks |
Optional character vector of additional
requirements to enforce on the introspection response when
|
authorization_server_mode |
Declares whether this client is part of an application that can interact with more than one authorization server, and which RFC 9700 mix-up defense it uses. One of:
|
authorization_server_redirect_uris |
Complete character vector of
redirect URIs used by the application for its authorization servers when
|
dpop_require_observed_cnf |
Logical. When |
jarm_signed_response_alg |
Optional expected JWS algorithm for
signed JWT Secured Authorization Responses (JARM). When omitted and the
effective response mode is JARM, shinyOAuth defaults to |
jarm_encrypted_response_alg |
Optional expected JWE
key-management algorithm for encrypted JARM responses. Current inbound
support is limited to |
jarm_encrypted_response_enc |
Optional expected JWE
content-encryption algorithm for encrypted JARM responses. Current inbound
support is limited to the AES-CBC-HMAC family ( |
jarm_decryption_private_key |
Optional private key
used to decrypt encrypted JARM responses. Can be an |
jarm_decryption_private_key_kid |
Optional key
identifier ( |
jarm_max_lifetime |
Positive number of seconds. Maximum accepted
lifetime for a JARM response JWT. Default is 600 seconds, matching JARM's
recommended 10-minute upper bound for authorization response JWTs. When a
JARM payload includes |
endpoint_auth |
Named list of authentication overrides for |
mtls_require_observed_cnf |
Logical, default |
trusted_id_token_audiences |
Character vector of additional ID-token
audiences explicitly trusted by this client. Defaults to |
compare_callback_issuer |
Logical or |
client_assertion_typ |
JWT header |
authorization_method |
Browser method for sending the authorization
request: |
resource_bases |
Optional named character vector of approved API base
URLs for |
required_scopes |
Optional requested scopes that every usable connection
needs, default |
label |
Optional display label used in connection summaries; defaults to
the provider name, with control characters replaced by spaces and shortened
to 128 UTF-8 bytes if needed. If the provider name is empty, missing or not a
single string, the default is |
... |
Deprecated renamed arguments accepted temporarily for backward compatibility. |
introspect_elements |
Compatibility alias for |
Details
Create the client outside server() so its settings and pending login state
remain available when the callback returns. Configure provider, client_id,
client_secret (if issued), redirect_uri, and scopes from the app
registration. Use state_store and state_key for shared login state across
workers, and validation arguments to require particular scopes, claims, or
authentication context. See the usage vignette for a complete app, or
the advanced security vignette for certificate and signed-request settings.
Value
OAuthClient object
Examples
# Register an app with GitHub and store its credentials in your environment.
# This creates the configuration; it does not start login or contact GitHub.
client <- oauth_client(
provider = oauth_provider_github(),
client_id = "your-client-id",
client_secret = "your-client-secret",
redirect_uri = "http://127.0.0.1:8100",
scopes = c("read:user", "user:email")
)
# In a real app, read credentials with Sys.getenv() and create client
# outside server(). Inside server(), start login with:
# auth <- oauth_module_server("auth", client)
Prepare client-certificate registration settings (mTLS)
Description
Build a list of settings to register a certificate-based client with your
provider using mutual TLS (mTLS). Use this when preparing metadata for dynamic
client registration or when your provider asks for certificate identifiers,
public keys, or certificate-bound token settings. It derives those settings
from an oauth_client() already configured for mTLS.
The result is a metadata list, ready to include in a registration request. Submit it through your provider's registration process; this function does not register the client or upload the certificate.
Usage
oauth_client_mtls_registration(
client,
tls_client_auth_type = c("subject_dn", "san_dns", "san_uri", "san_ip", "san_email"),
tls_client_auth_value = NULL,
jwks_uri = NULL,
oauth_client = NULL
)
Arguments
client |
OAuthClient configured for RFC 8705 mutual TLS client authentication or for certificate-bound access tokens. |
tls_client_auth_type |
For |
tls_client_auth_value |
Optional explicit value for the selected
|
jwks_uri |
Optional absolute URL of a JWKS document to publish for
|
oauth_client |
Compatibility alias for |
Details
For tls_client_auth, the result identifies the client certificate using
one selected subject or alternative-name field. For
self_signed_tls_client_auth, it contains an inline jwks with the
certificate chain (x5c), or the supplied jwks_uri.
For certificate-bound tokens without mTLS client authentication, the result
uses the corresponding registration authentication method (for example,
public becomes none) and sets
tls_client_certificate_bound_access_tokens = TRUE. See
the advanced security vignette for when these configurations are useful.
Value
A JSON-ready list of RFC 7591/RFC 8705 client metadata.
Examples
# Set these environment variables to your existing certificate and key files.
provider <- oauth_provider(
name = "Example service",
auth_url = "https://example.com/authorize",
token_url = "https://example.com/token",
token_auth_style = "tls_client_auth"
)
client <- oauth_client(
provider = provider,
client_id = "example-client",
redirect_uri = "http://127.0.0.1:8100/callback",
mtls_client_cert_file = Sys.getenv("OAUTH_MTLS_CERT_FILE"),
mtls_client_key_file = Sys.getenv("OAUTH_MTLS_KEY_FILE")
)
oauth_client_mtls_registration(client)
Create a client secret for Sign in with Apple
Description
Use your Apple developer key to create the client secret expected by Sign in
with Apple. Pass the returned string as client_secret to oauth_client().
Unlike a fixed password, this secret expires; replace it before its expiry.
Usage
oauth_client_secret_apple(
client_id,
team_id,
key_id,
private_key,
expires_in = 15776700,
issued_at = Sys.time(),
audience = "https://appleid.apple.com"
)
Arguments
client_id |
Apple Services ID or App ID used as the OAuth client id |
team_id |
Apple Developer Team ID. Apple documents this as a 10-character identifier |
key_id |
Apple Sign in with Apple private-key identifier ( |
private_key |
Apple private key as an |
expires_in |
Positive lifetime in seconds. Must be no more than
|
issued_at |
Issue time for the JWT. Defaults to |
audience |
Audience claim. Defaults to |
Details
The helper signs a JWT with ES256. It places your Team ID in iss, your
client ID in sub, Apple's URL in aud, and the key ID in the header.
The lifetime must not exceed 15,777,000 seconds (about six months).
Value
A compact signed JWT string suitable for oauth_client(..., client_secret = ...)
Examples
## Not run:
key <- openssl::ec_keygen(curve = "P-256")
oauth_client_secret_apple(
client_id = "com.example.web",
team_id = "ABCDEFGHIJ",
key_id = "ABC123DEFG",
private_key = key
)
## End(Not run)
Make API requests with a Shiny session's current OAuth credentials
Description
Combine one module's reactive token with its client and approved API addresses
configured on oauth_client(). Call [["request"]]() on the returned connection instead of
assembling a token, client and URL for each request. It reads the reactive token
again after refresh or logout and restricts requests to the configured APIs.
This optional wrapper expires with its Shiny session; it does not implement
refresh itself or retain credentials across redirects.
Usage
oauth_connection(
client,
token_reactive,
session = shiny::getDefaultReactiveDomain()
)
Arguments
client |
An OAuthClient with non-empty |
token_reactive |
A Shiny reactive expression returning the current OAuthToken
or |
session |
The owning Shiny session; defaults to the current session. |
Details
Create the reference once inside server(). Access it only in that session's
reactive context. [["summary"]]() excludes tokens, identity claims and extension
context. [["is_usable"]]() checks local presence, known expiry and required scopes;
it cannot guarantee remote authorization. Unknown token expiry is unusable.
Paths are relative to the selected base directory. Absolute and root-relative references (including pagination links) must stay within that same base. Dot segments and ambiguous encodings are rejected; redirects are never followed. Bearer, DPoP and mTLS use the existing transport and the configured client.
Use request-level required_scopes for optional operations. They must be
included in the client's requested scopes and covered by the current grant.
The package cannot infer arbitrary API permissions from an HTTP method/path.
The legacy module continues to own refresh and logout. Retained connection
storage and its independent lifecycle are available through
oauth_connections() and oauth_connections_server().
Value
An OAuthConnection with [["id"]], [["is_usable"]](), [["summary"]]() and
[["request"]](resource_id, path = "", query = NULL, method = "GET", required_scopes = character(), configure = NULL). Requests return httr2
responses. configure can add a body and application headers; see
OAuthConnection for its contract.
Examples
## Not run:
# Configure outside server():
client <- oauth_client(provider, "registered-app",
redirect_uri = "https://app.example/callback", scopes = "read",
resource_bases = c(api = "https://api.example/v1"))
# Inside server():
auth <- oauth_module_server("auth", client)
connection <- oauth_connection(client, shiny::reactive(auth[["token"]]))
data <- shiny::reactive({
shiny::req(connection[["is_usable"]]())
connection[["request"]]("api", "records", required_scopes = "read")
})
## End(Not run)
Create a process-local store for retained OAuth connections
Description
Stores encrypted credential envelopes and coordinates their lifecycle across
Shiny sessions in one R process. Create one store outside server(). This
adapter does not survive process restarts and rejects use from a copied worker
process. It is a connection-manager building block, not an authentication API.
Usage
oauth_connection_store_memory(
max_age = 28800,
refresh_timeout = 60,
max_entries = 1000L
)
## S3 method for class 'OAuthConnectionStore'
print(x, ...)
Arguments
max_age |
Maximum record and transaction-deduplication lifetime in seconds. Each record must also supply an earlier or equal absolute expiry. |
refresh_timeout |
Maximum time in seconds for a claimed refresh. An abandoned operation becomes uncertain; its old credentials cannot be retried. |
max_entries |
Maximum live record and transaction-reservation count, including tombstones and recently expired transactions. A full store rejects creation instead of evicting another connection. |
x |
An |
... |
Unused print arguments. |
Details
Methods are trusted server-side operations. The manager must validate the current owner session and generation before calling them. The opaque owner identifier scopes records and can survive session rotation; knowledge of an owner or connection ID does not authenticate a user. Records contain ciphertext only. Encryption keys stay with the manager, outside this adapter. Never expose these methods or credential imports as HTTP routes.
-
[["create"]](owner, id, transaction, client, fingerprint, sealed, expires_at)returns a record with a new revision, orNULLfor a duplicate connection/transaction. -
[["read"]](owner, id)returns the owner's record, orNULLwhen absent, expired, or owned by someone else. It includes the sealed envelope for internal use. -
[["list"]](owner)returns metadata lists without ciphertext or operation IDs. -
[["begin_refresh"]](owner, id, revision)claims an active record and returns its new revision and operation ID. A conflicting claim returnsNULL. -
[["commit_refresh"]](owner, id, operation, revision, sealed)installs credentials only for the current claim, returning the updated record orNULL. -
[["fail_refresh"]](owner, id, operation, revision, outcome)releases a claim only for"not_consumed";"possibly_consumed"and"consumed"remove the old envelope and mark the record"uncertain". Returns the record orNULL. -
[["disconnect"]](owner, id, revision)first installs a credential-free tombstone, then returnslist(record, previous)for bounded remote cleanup. Conflicts returnNULL; the caller must reload before retrying. -
[["disconnect_owner"]](owner)tombstones all of that owner's records and returns the previous records for bounded cleanup. Other owners are unaffected.
Reads expire abandoned refresh claims before returning data. Tombstones and transaction IDs remain at least until the original absolute expiry so late completions cannot restore a disconnected grant. Operations are synchronous and do not yield or perform network calls. Atomicity applies to this R process only. A separate shared backend needs its own verified concurrency contract.
Value
An OAuthConnectionStore adapter with the methods described below.
See Also
oauth_client(), oauth_connection()
Examples
# Create once, outside server(), then supply to oauth_connections().
store <- oauth_connection_store_memory(max_age = 8 * 3600, max_entries = 100)
Configure several independently managed OAuth connections
Description
Create one manager outside server() for a named set of client/API
configurations from oauth_client() or smart_client(). Use oauth_connections_ui() to handle
callbacks and oauth_connections_server() for each Shiny session. Each
successful authorization creates a separate connection, including repeated
authorizations at the same client.
Connections are independent local records. Providers may reuse an upstream
grant, so revoking one can invalidate tokens held by other connections.
Within a manager, refreshes sharing one credential are serialized. Rotation
or an uncertain refresh invalidates other records holding the same credential;
authorize those records again. Their identity and permissions are never
replaced by another connection's response. Successful revocation also
invalidates known copies of that credential. Distinct tokens may still share
an upstream grant whose revocation effects the manager cannot predict.
Retired credential digests survive connection replacement and expiry for at
least the store's maximum age and the longest client state lifetime. A separate
registry holds at most 10,000 digests or pending reservations. A full registry
rejects refresh or skips remote revocation before sending credentials; it does
not evict retirement evidence. Existing unrelated access remains usable.
Usage
oauth_connections(
clients,
app_origin,
retention = c("shiny", "browser", "account"),
retention_seconds = 28800,
owner_policy = NULL,
store = NULL,
keys = NULL,
callback_policy = "distinct_routes"
)
## S3 method for class 'OAuthConnections'
print(x, ...)
Arguments
clients |
Non-empty named list of OAuthClient objects, at most 64.
Each client must configure non-empty |
app_origin |
Public application origin, including a non-default port. HTTPS is required for retained owners, except an explicit browser-owner HTTP loopback exception. Session-only development also permits loopback HTTP. |
retention |
|
retention_seconds |
Maximum lifetime of each stored grant, in seconds.
Positive and finite, no larger than the store's |
owner_policy |
|
store |
A store from |
keys |
Named list with |
callback_policy |
|
x |
A connection manager to print. |
... |
Unused print arguments. |
Details
A manager is bound to one UI/server module ID and one public origin. Create another manager for another namespace. The memory store and owner registries survive Shiny sessions, not R restarts; copies in another R process fail closed.
In session-only mode, a pending authorization survives navigation through the existing single-use OAuth state and browser binding, while existing grants are discarded with the old Shiny session. Browser/account mode additionally binds authorization to the initiating local owner and its session generation.
These are optional package interfaces, not SMART protocol objects. This manager
supports generic OAuth clients and the SMART discovery, scope and launch
policies configured by smart_client().
Value
An OAuthConnections server-side configuration object. Printing shows
only the retention mode and client count. It contains client configuration
and deployment keys and must never be sent to the browser.
See Also
oauth_connections_ui(), oauth_connections_server()
Examples
# Replace this example provider and client ID with your registered application.
provider <- oauth_provider(
name = "Example service",
auth_url = "https://example.com/authorize",
token_url = "https://example.com/token",
token_auth_style = "public"
)
client <- oauth_client(
provider = provider,
client_id = "example-client",
redirect_uri = "http://127.0.0.1:8100/callback/service",
resource_bases = c(api = "https://api.example.com")
)
# Create the manager once, outside server(). Default retention is one Shiny session.
manager <- oauth_connections(
clients = list(service = client),
app_origin = "http://127.0.0.1:8100"
)
ui <- oauth_connections_ui(
shiny::fluidPage(
shiny::actionButton("connect", "Connect to service"),
shiny::verbatimTextOutput("connections")
),
id = "auth",
manager = manager
)
server <- function(input, output, session) {
auth <- oauth_connections_server("auth", manager)
shiny::observeEvent(input[["connect"]], auth[["connect"]]("service"))
output[["connections"]] <- shiny::renderPrint(auth[["connections"]]())
}
# Construct the app without launching it or contacting the provider.
app <- shiny::shinyApp(
ui,
server,
uiPattern = ".*",
options = list(port = 8100)
)
Connect, restore and use several OAuth authorizations in a Shiny session
Description
Call once inside server() with the manager and ID used by
oauth_connections_ui(). The returned methods select stored connections by
opaque IDs and recheck the local owner for every operation. Tokens are kept
on the server and are not returned by summaries.
Usage
oauth_connections_server(
id,
manager,
async = FALSE,
refresh_proactively = FALSE,
refresh_lead_seconds = 60,
refresh_check_interval_ms = 10000
)
Arguments
id |
Module ID shared with |
manager |
Configuration from |
async |
Whether authorization and refresh use the existing async worker transport. Owner checks and store mutations stay in the original R process. |
refresh_proactively |
Refresh before expiry when a refresh credential is available. Otherwise the manager attempts refresh at expiry. |
refresh_lead_seconds |
Non-negative number of seconds before expiry used for proactive refresh. Background checks never extend owner inactivity limits. |
refresh_check_interval_ms |
Positive polling interval in milliseconds, at least 100. Safely retryable automatic refresh failures wait at least 30 seconds across all connections sharing the same refresh credential and registration; an uncertain refresh requires reconnecting. |
Details
References expire with this Shiny session even when their stored grants survive.
A new session obtains new references after owner verification. The manager
coordinates refresh across its connections. Refresh preserves the original
authentication time and retention expiry.
Reactive connection reads also recheck expiry at refresh_check_interval_ms,
including references used without connections() or errors(). These checks
notify dependent expressions when lifecycle state changes; unchanged polling
does not rerun application requests or extend owner inactivity limits.
Notifications to application code reflect only this owner's record changes.
Resource requests and status reads never reset owner inactivity, including when
reactive expressions rerun after automatic refresh. Call touch() from a user
input event handler to count an application action as activity. Do not call it
from polling observers or ordinary reactive readers. Connecting, explicitly
refreshing and disconnecting also count as activity.
Remote revocation is best effort: at most ten seconds per disconnect/logout
batch, at most two seconds and one HTTP attempt per credential. Results are
accepted, unsupported, missing, failed or not_attempted for access and
refresh credentials. accepted describes the endpoint response, not proof of
prior token validity. Local disconnect remains effective if revocation fails.
A provider may revoke an entire authorization grant or related credentials
(RFC 7009 section 2.1). Separate connection IDs do not establish independent
provider grants, even after repeated consent or account selection. Consequently,
default revoke = TRUE may also end access for sibling connections or other
applications covered by the provider's revocation policy. Use revoke = FALSE
when preserving those authorizations is required. This also applies to
credentials returned by work already in flight. Removed credentials remain
valid remotely until the provider expires
or revokes them. Local summaries and is_usable() do not detect such remote
changes: handle API authorization failures and obtain a new authorization.
Use this API inside its owning session's reactive context. Session setup requires a matching HTTP Origin on the Shiny request. Raw HTTP routes cannot import credentials or select an owner. The manager supports one R process.
Value
A server-side list with:
-
connect(client_name): request a new authorization without discarding others. EHR-only clients reportfresh_ehr_launch_requiredand returnFALSE; use their registeredsmart_launch_route()to start authorization. -
connections(): reactive list of redacted connection summaries. -
connection(connection_id): an OAuthConnection for requests and refresh. -
touch(): record explicit user activity after checking the current owner. Call from an input event handler; returnsTRUEinvisibly. -
disconnect(connection_id, revoke = TRUE): remove local usability first, then return separatelocalandremoterevocation results. -
disconnect_all(revoke = TRUE): cancel pending authorizations and disconnect this owner's stored connections; return a list of results. -
logout(revoke = TRUE, reload = TRUE): invalidate the local owner/session generation first, disconnect its connections, and normally reload the UI. This does not log the user out of the external OAuth provider or the app's own account authentication system. -
errors(): reactive list of per-client module error codes, with no raw provider text. An ended owner is reported asowner_unavailable.
Examples
# Replace this example provider and client ID with your registered application.
provider <- oauth_provider(
name = "Example service",
auth_url = "https://example.com/authorize",
token_url = "https://example.com/token",
token_auth_style = "public"
)
client <- oauth_client(
provider = provider,
client_id = "example-client",
redirect_uri = "http://127.0.0.1:8100/callback/service",
resource_bases = c(api = "https://api.example.com")
)
# Create the manager once, outside server(). Default retention is one Shiny session.
manager <- oauth_connections(
clients = list(service = client),
app_origin = "http://127.0.0.1:8100"
)
ui <- oauth_connections_ui(
shiny::fluidPage(
shiny::actionButton("connect", "Connect to service"),
shiny::verbatimTextOutput("connections")
),
id = "auth",
manager = manager
)
server <- function(input, output, session) {
auth <- oauth_connections_server("auth", manager)
shiny::observeEvent(input[["connect"]], auth[["connect"]]("service"))
output[["connections"]] <- shiny::renderPrint(auth[["connections"]]())
}
# Construct the app without launching it or contacting the provider.
app <- shiny::shinyApp(
ui,
server,
uiPattern = ".*",
options = list(port = 8100)
)
Handle callbacks and establish the connection owner's browser session
Description
Wrap the application's UI with the same manager and module ID used by
oauth_connections_server(). Browser retention establishes its HttpOnly
owner cookie on an ordinary page request before Shiny starts. OAuth callbacks
use the existing validated callback bridge and clean continuation.
Usage
oauth_connections_ui(
base_ui,
id,
manager,
request_uri_resolver = NULL,
app_base_path = "/",
launch_routes = list(),
additional_clients = list()
)
Arguments
base_ui |
A Shiny UI object or request-dependent UI function. |
id |
Module ID shared with |
manager |
Configuration from |
request_uri_resolver |
Optional trusted function mapping a Rook request to its public absolute URI. Required when a trusted reverse proxy changes the apparent scheme/host. Do not trust arbitrary forwarded headers. The resolved origin must match the manager's configured origin. |
app_base_path |
Public path at which the Shiny application is hosted,
default |
launch_routes |
List of |
additional_clients |
Optional named list of ordinary OAuth/OIDC clients
used by separate |
Details
Raw GET and form POST callbacks never create or rotate an owner. A POST may
lack a SameSite owner cookie. With Strict owner cookies, a validated callback
first serves an inert same-origin document that navigates to the clean
continuation, allowing the browser to send its existing cookie. The document
does not establish an owner or exchange credentials. A managed continuation
must carry a still-valid owner before credentials can be exchanged.
A validated ordinary callback for
additional_clients can establish a new empty manager owner independently.
An invalid cookie on an ordinary
page is cleared using an HTTP response and a same-origin redirect; the next
request establishes a new empty browser owner.
The cookie is scoped to its host. Separate applications on that host must be trusted; different ports or paths do not isolate their cookies. The wrapper adds no owner cookie for session-only or account retention.
Value
A request UI function for shinyApp(..., uiPattern = ".*").
See Also
oauth_browser_owner(), oauth_ui()
Examples
# Replace this example provider and client ID with your registered application.
provider <- oauth_provider(
name = "Example service",
auth_url = "https://example.com/authorize",
token_url = "https://example.com/token",
token_auth_style = "public"
)
client <- oauth_client(
provider = provider,
client_id = "example-client",
redirect_uri = "http://127.0.0.1:8100/callback/service",
resource_bases = c(api = "https://api.example.com")
)
# Create the manager once, outside server(). Default retention is one Shiny session.
manager <- oauth_connections(
clients = list(service = client),
app_origin = "http://127.0.0.1:8100"
)
ui <- oauth_connections_ui(
shiny::fluidPage(
shiny::actionButton("connect", "Connect to service"),
shiny::verbatimTextOutput("connections")
),
id = "auth",
manager = manager
)
server <- function(input, output, session) {
auth <- oauth_connections_server("auth", manager)
shiny::observeEvent(input[["connect"]], auth[["connect"]]("service"))
output[["connections"]] <- shiny::renderPrint(auth[["connections"]]())
}
# Construct the app without launching it or contacting the provider.
app <- shiny::shinyApp(
ui,
server,
uiPattern = ".*",
options = list(port = 8100)
)
Wrap a Shiny UI to enable OAuth 2.0/OIDC form_post callbacks
Description
Accept the provider's login callback as an HTTP POST and continue login with
oauth_module_server(). Use this when you select response_mode = "form_post"
or "form_post.jwt", either because the provider requires it or to keep
callback parameters out of the browser URL. The POST arrives before a Shiny
session exists; this wrapper receives it and makes the validated callback
available to the server module. For query-string callbacks, use oauth_ui().
Usage
oauth_form_post_ui(
base_ui,
id = NULL,
client = NULL,
callback_path = NULL,
request_uri_resolver = NULL,
clients = NULL
)
Arguments
base_ui |
Existing Shiny UI object, or a UI function accepting |
id |
Shiny module id used by |
client |
OAuthClient object used by |
callback_path |
Optional URL path to accept POST callbacks on. Defaults
to the path component of |
request_uri_resolver |
Optional function accepting the Rook |
clients |
Optional client registry as in |
Details
Set response_mode on your oauth_client(), wrap your UI here, and use
the same id and client in oauth_module_server(). The wrapper includes
the browser setup and privacy header supplied by oauth_ui().
For a callback path below the app root, pass uiPattern = ".*" to
shiny::shinyApp() so Shiny routes the callback to this wrapper.
The wrapper checks the incoming address and login state, stores the callback
temporarily, and redirects the browser to a normal Shiny page with a
single-use handle. Raw callback values do not appear in that redirected URL.
For "form_post.jwt", it also validates the signed response using
JWT Secured Authorization Response Mode (JARM).
Every POST receives its own random handle. Handles expire after 120 seconds
or the smaller of state_payload_max_age and the state store lifetime.
The module still verifies the browser binding before consuming login state.
Each client/provider/module namespace has a separate bounded pool in the
state store: 256 partitions of
eight slots, with each login assigned to one partition. When a partition is
full, a new POST replaces its oldest response. An expired or replaced handle
fails validation; it can never select the replacement response. Completed
logins invalidate their remaining candidates and remove locally known
candidates where possible. Shared stores still require atomic take.
With set_if_absent, slots are claimed atomically with a short TTL; a full
partition rejects new callbacks until a slot is consumed or expires.
Otherwise, concurrent writers can also
replace a candidate; handle validation fails closed in that case. Pending
form-post handles issued by an older version must be restarted after upgrading.
Value
A Shiny UI function. Pass it to shiny::shinyApp() and, for non-root
callback paths, use uiPattern = ".*" so Shiny routes the callback path to
this UI function.
Deployment behind a proxy
If a proxy receives HTTPS and forwards HTTP to Shiny, supply a
request_uri_resolver that reconstructs the public address after verifying
the request came from your trusted proxy. The default resolver does not
trust forwarded headers. The resulting address must match the configured
redirect origin and callback path. See the advanced security vignette.
Callback size limits are documented in the package options reference.
Examples
if (
# Example requires a local or remote Keycloak realm whose client allows
# http://127.0.0.1:8100/callback as a valid redirect URI.
nzchar(Sys.getenv("KEYCLOAK_BASE_URL")) &&
nzchar(Sys.getenv("KEYCLOAK_REALM")) &&
nzchar(Sys.getenv("KEYCLOAK_CLIENT_ID")) &&
interactive()
) {
library(shiny)
library(shinyOAuth)
options(shinyOAuth.allow_insecure_oidc_loopback = TRUE)
provider <- oauth_provider_keycloak(
base_url = Sys.getenv("KEYCLOAK_BASE_URL"),
realm = Sys.getenv("KEYCLOAK_REALM")
)
client <- oauth_client(
provider = provider,
client_id = Sys.getenv("KEYCLOAK_CLIENT_ID"),
client_secret = Sys.getenv("KEYCLOAK_CLIENT_SECRET"),
redirect_uri = "http://127.0.0.1:8100/callback",
scopes = c("openid", "profile", "email"),
response_mode = "form_post"
)
base_ui <- fluidPage(
uiOutput("login")
)
ui <- oauth_form_post_ui(base_ui, id = "auth", client = client)
server <- function(input, output, session) {
auth <- oauth_module_server("auth", client, auto_redirect = TRUE)
output[["login"]] <- renderUI({
if (auth[["authenticated"]]) {
user_info <- auth[["token"]]@userinfo
tagList(
tags[["p"]]("You are logged in!"),
tags[["pre"]](paste(capture.output(str(user_info)), collapse = "\n"))
)
} else {
tags[["p"]]("You are not logged in.")
}
})
}
runApp(
shinyApp(ui, server, uiPattern = ".*"),
port = 8100,
launch.browser = FALSE
)
}
OAuth 2.0 authorization and OIDC authentication module for Shiny
Description
Call oauth_module_server() inside your Shiny server() function to manage
login for each user. It sends users to the provider, checks their return,
and gives your app reactive login status and user information.
Create client with oauth_client() outside server(), and wrap your
complete UI with oauth_ui().
This uses the OAuth 2.0 Authorization Code flow, with OpenID Connect (OIDC) identity checks when configured for an OIDC provider.
Usage
oauth_module_server(
id,
client,
auto_redirect = TRUE,
async = FALSE,
indefinite_session = FALSE,
reauth_after_seconds = NULL,
refresh_proactively = FALSE,
refresh_lead_seconds = 60,
refresh_check_interval_ms = 10000,
revoke_on_session_end = FALSE,
tab_title_cleaning = TRUE,
tab_title_replacement = NULL,
request_uri_base_url = NULL,
browser_cookie_path = NULL,
browser_cookie_samesite = c("Strict", "Lax", "None"),
refresh_check_interval = NULL
)
Arguments
id |
A name for this Shiny module, such as |
client |
The app configuration created with |
auto_redirect |
If |
async |
If |
indefinite_session |
If TRUE, the module will not automatically clear
the token due to access-token expiry or the |
reauth_after_seconds |
Optional maximum interactive-authentication age
in seconds. If set, the module removes the token (and thus sets
|
refresh_proactively |
If |
refresh_lead_seconds |
Number of seconds before expiry to attempt proactive refresh (default: 60) |
refresh_check_interval_ms |
Fallback interval in milliseconds for checking expiry and refresh (default 10000). Known expiry times are scheduled directly; this interval is used as a safety check or when expiry is unknown or infinite. |
revoke_on_session_end |
If TRUE, automatically revokes provider tokens
when the Shiny session ends (e.g., browser tab closed, session timeout).
This is a best-effort operation. Revocation runs asynchronously only when
the module is configured with |
tab_title_cleaning |
If TRUE (default), removes any query string suffix from the browser tab title after the OAuth callback, so titles like "localhost:8100?code=...&state=..." become "localhost:8100" |
tab_title_replacement |
Optional character string to explicitly set the
browser tab title after the OAuth callback. If provided, it takes
precedence over |
request_uri_base_url |
Optional absolute base URL used when
|
browser_cookie_path |
URL path covered by the login cookie. Default |
browser_cookie_samesite |
Cookie setting controlling when the browser sends
the login cookie on requests from other sites. One of |
refresh_check_interval |
Compatibility alias for |
Details
Login starts automatically by default. Use auto_redirect = FALSE and
auth[["request_login"]]() to start it from a button. Read auth[["authenticated"]]
in reactive code, and use req(auth[["authenticated"]]) before server operations
that require login. Your app must also enforce its own access rules.
See the usage vignette for a complete app and instructions for registration, API calls, and deployment.
Value
A shiny::reactiveValues() object. If you assign it to auth,
its main fields are:
-
auth[["authenticated"]]:TRUEwhen a token is present and the configured checks have passed, otherwiseFALSE. Withindefinite_session = TRUE, the flag stays true while a token is kept, including after refresh errors. -
auth[["token"]]: an OAuthToken, orNULLbefore login or after clearing the session. Read properties with@, for exampleauth[["token"]]@userinfo. Additional token response parameters are available inauth[["token"]]@extra_fields;auth[["token"]]@initial_extra_fieldspreserves the parameters from the initial code exchange across refreshes. -
auth[["error"]],auth[["error_description"]]: the error code and available diagnostic detail. Use your own user-facing message; these fields can include sensitive provider information. -
auth[["error_uri"]]: an optional provider help URL. Only absolute HTTPS URLs on provider or explicitly allowed hosts are surfaced. Treat it as untrusted navigation input.NULLmeans the provider omitted the URL or supplied a value that did not pass validation. -
auth[["token_stale"]]:TRUEwhen an indefinite session keeps an expired token or one whose refresh failed. Resets after successful login, refresh, or logout.
The object also supplies:
-
auth[["request_login"]](): start login. Waits for browser setup when needed and does nothing if the session is already authenticated. Uses a browser form when the client selectsauthorization_method = "POST"; the app's Content Security Policyform-actionmust permit the provider endpoint. -
auth[["logout"]](): clear the local login and attempt to revoke tokens when supported, followingasync. It does not sign out of the provider account. -
auth[["build_auth_url"]](): advanced helper for a custom login link. Rejects POST clients; userequest_login()for their form submission. Creates pending login state as well as the URL, so retain the result for the link instead of rebuilding it on every UI update. Rotates and checks the browser binding before creating state. Returns a promise resolving to the URL (orNAon failure or an obsolete result); usepromises::then(). PAR URLs carryshinyOAuth.par_request_uri,shinyOAuth.par_expires_in, andshinyOAuth.par_expires_atattributes to help you decide when to regenerate the link.request_login()handles these details for button-based login. InsideobserveEvent(), register the promise handler and then returninvisible(NULL)so Shiny can process the browser acknowledgment. Do not return the pending promise from the observer itself. -
auth[["has_browser_token"]](): reports whether the browser token is available. Use it before building a custom login URL; it does not report whether the user is authenticated. -
auth[["set_browser_token"]](): asks the browser to establish its binding when missing. The token becomes available after the browser reports it back to Shiny. An existing token is left unchanged. -
auth[["clear_browser_token"]](): clears the browser binding.request_login()manages cookie setup automatically, andlogout()handles cookie rotation when ending a session.
Other fields manage the module internally and are not needed in app code.
Asynchronous execution
With async = TRUE, configure mirai::daemons() or a non-sequential
future::plan() before starting the app. Slow provider requests can then
run outside the main R process. Without this, network waits can delay all
Shiny sessions sharing that process.
Advanced operations sent to workers include PAR, signed Request Object
preparation, and query JARM verification. State-store operations and Shiny
Request Object publication stay in the main process. Discovery during app
setup, standalone prepare_call(), and JARM verification in
oauth_form_post_ui() remain synchronous. Use timeouts on those network
and storage operations; see the package options reference.
Browser setup
Open the app at its registered return address in a regular browser with
cookies, session storage, and Web Crypto enabled. Embedded IDE viewers may
prevent login. The binding token stays in origin- and tab-scoped session storage; the
cookie contains an independent marker, which must match the stored record.
The temporary browser cookie follows the state store's max_age, with a
300-second fallback when that lifetime is unavailable. The separate
state_payload_max_age client setting limits the age of the login request.
Each new login uses a fresh server-selected browser binding and its own marker
cookie. Application callback routes and module namespaces identify the storage
record. Separate tabs can complete logins independently; complete a login in
the tab that started it. Starting another login in the same tab and module
replaces that tab's pending binding. Pending logins must be restarted after
upgrading from versions that used local storage.
Private browser-binding inputs are excluded from Shiny bookmarks. Do not
copy auth[["browser_token"]] into custom bookmark values, URLs, or logs.
Treat the entire hostname as a trust boundary: cookies are shared across
ports, even with __Host-, Secure, or HttpOnly. Use a dedicated hostname
when other services are not trusted. The origin-scoped check prevents cookie
adoption across ports, but co-hosted services can still disrupt cookies.
See Also
oauth_ui(), oauth_client(), OAuthToken, oauth_form_post_ui()
Examples
# Register http://127.0.0.1:8100 as the GitHub OAuth App callback URL.
if (
# Example requires configured GitHub OAuth 2.0 app
# (go to https://github.com/settings/developers to create one):
nzchar(Sys.getenv("GITHUB_OAUTH_CLIENT_ID")) &&
nzchar(Sys.getenv("GITHUB_OAUTH_CLIENT_SECRET")) &&
interactive()
) {
library(shiny)
library(shinyOAuth)
# Define client
client <- oauth_client(
provider = oauth_provider_github(),
client_id = Sys.getenv("GITHUB_OAUTH_CLIENT_ID"),
client_secret = Sys.getenv("GITHUB_OAUTH_CLIENT_SECRET"),
redirect_uri = "http://127.0.0.1:8100",
scopes = c("read:user", "user:email")
)
# Choose which app you want to run
app_to_run <- NULL
while (!isTRUE(app_to_run %in% c(1:4))) {
app_to_run <- readline(
prompt = paste0(
"Which example app do you want to run?\n",
" 1: Auto-redirect login\n",
" 2: Manual login button\n",
" 3: Fetch additional resource with access token\n",
" 4: No app (all will be defined but none run)\n",
"Enter 1, 2, 3, or 4... "
)
)
}
if (app_to_run %in% c(1:3)) {
cli::cli_alert_info(paste0(
"Will run example app {app_to_run} on {.url http://127.0.0.1:8100}\n",
"Open this URL in a regular browser (viewers in RStudio/Positron/etc. ",
"cannot perform necessary redirects)"
))
}
# Example app with auto-redirect (1) -----------------------------------------
ui_1 <- oauth_ui(
fluidPage(
uiOutput("login")
),
id = "auth",
client = client
)
server_1 <- function(input, output, session) {
# Auto-redirect (default):
auth <- oauth_module_server(
"auth",
client,
auto_redirect = TRUE
)
output[["login"]] <- renderUI({
if (auth[["authenticated"]]) {
user_info <- auth[["token"]]@userinfo
tagList(
tags[["p"]]("You are logged in!"),
tags[["pre"]](paste(capture.output(str(user_info)), collapse = "\n"))
)
} else {
tags[["p"]]("You are not logged in.")
}
})
}
app_1 <- shinyApp(ui_1, server_1)
if (app_to_run == "1") {
runApp(
app_1,
port = 8100,
launch.browser = FALSE
)
}
# Example app with manual login button (2) -----------------------------------
ui_2 <- oauth_ui(
fluidPage(
actionButton("login_btn", "Login"),
actionButton("logout_btn", "Logout"),
uiOutput("login")
),
id = "auth",
client = client
)
server_2 <- function(input, output, session) {
auth <- oauth_module_server(
"auth",
client,
auto_redirect = FALSE
)
observeEvent(input[["login_btn"]], {
auth[["request_login"]]()
})
observeEvent(input[["logout_btn"]], {
auth[["logout"]]()
})
output[["login"]] <- renderUI({
if (auth[["authenticated"]]) {
user_info <- auth[["token"]]@userinfo
tagList(
tags[["p"]]("You are logged in!"),
tags[["pre"]](paste(capture.output(str(user_info)), collapse = "\n"))
)
} else {
tags[["p"]]("You are not logged in.")
}
})
}
app_2 <- shinyApp(ui_2, server_2)
if (app_to_run == "2") {
runApp(
app_2,
port = 8100,
launch.browser = FALSE
)
}
# Example app requesting additional resource with access token (3) -----------
# Below app shows the authenticated username + their GitHub repositories,
# fetched via GitHub API using the access token obtained during login
ui_3 <- oauth_ui(
fluidPage(
uiOutput("ui")
),
id = "auth",
client = client
)
server_3 <- function(input, output, session) {
auth <- oauth_module_server(
"auth",
client,
auto_redirect = TRUE
)
repositories <- reactiveVal(NULL)
repository_error <- reactiveVal(FALSE)
observe({
req(auth[["authenticated"]])
# Example additional API request using the access token
# (e.g., fetch user repositories from GitHub)
# This loads one page; use the API's pagination for further results.
repos_data <- tryCatch(
{
resp <- perform_resource_req(
auth[["token"]],
"https://api.github.com/user/repos",
query = list(per_page = 30)
)
httr2::resp_check_status(resp)
httr2::resp_body_json(resp, simplifyVector = TRUE)
},
error = function(e) NULL
)
repository_error(is.null(repos_data))
repositories(repos_data)
})
# Render username + their repositories
output[["ui"]] <- renderUI({
if (isTRUE(auth[["authenticated"]])) {
user_info <- auth[["token"]]@userinfo
repos <- repositories()
return(tagList(
tags[["p"]](paste("You are logged in as:", user_info[["login"]])),
tags[["h4"]]("Your repositories:"),
if (repository_error()) {
tags[["p"]]("Could not load repositories.")
} else if (!is.null(repos) && length(repos) == 0) {
tags[["p"]]("No repositories returned.")
} else if (!is.null(repos)) {
tags[["ul"]](
Map(
function(url, name) {
# Render names as text; accept only GitHub HTTPS links.
if (isTRUE(grepl("^https://github\\.com/", url))) {
tags[["li"]](tags[["a"]](
href = url,
target = "_blank",
rel = "noopener noreferrer",
name
))
} else {
tags[["li"]](name)
}
},
repos[["html_url"]],
repos[["full_name"]]
)
)
} else {
tags[["p"]]("Loading repositories...")
}
))
}
return(tags[["p"]]("You are not logged in."))
})
}
app_3 <- shinyApp(ui_3, server_3)
if (app_to_run == "3") {
runApp(
app_3,
port = 8100,
launch.browser = FALSE
)
}
}
Configure OAuth/OIDC provider endpoints and validation settings
Description
Configure a service from its documented endpoint URLs and protocol settings
when no named provider helper fits or OIDC discovery is unavailable.
Pass the resulting provider to oauth_client().
For a supported service, its named helper is an easier starting point;
for OIDC, oauth_provider_oidc_discover() can look up the settings.
Usage
oauth_provider(
name,
auth_url,
token_url,
userinfo_url = NA_character_,
introspection_url = NA_character_,
revocation_url = NA_character_,
par_url = NA_character_,
par_required = FALSE,
authorization_request_front_channel_mode = "compat",
request_object_signing_alg_values_supported = character(),
request_object_encryption_alg_values_supported = character(),
request_object_encryption_enc_values_supported = character(),
request_object_encryption_jwk = NULL,
signed_request_object_required = FALSE,
request_parameter_supported = NA,
request_uri_parameter_supported = NA,
request_uri_registration_required = NA,
token_endpoint_auth_signing_alg_values_supported = character(),
dpop_signing_alg_values_supported = character(),
authorization_response_iss_parameter_supported = FALSE,
response_modes_supported = character(),
mtls_endpoint_aliases = list(),
mtls_client_certificate_bound_access_tokens = FALSE,
issuer = NA_character_,
issuer_match = "url",
use_nonce = NULL,
use_pkce = TRUE,
pkce_method = "S256",
userinfo_required = NULL,
userinfo_id_token_match = NULL,
userinfo_signed_jwt_required = FALSE,
userinfo_id_selector = function(userinfo) {
userinfo[["sub"]]
},
id_token_required = NULL,
id_token_validation = NULL,
extra_auth_params = list(),
extra_token_params = list(),
extra_token_headers = character(),
token_auth_style = "header",
jwks_cache = NULL,
jwks_pins = character(),
jwks_pin_mode = "any",
jwks_host_issuer_match = NULL,
jwks_host_allow_only = NULL,
id_token_allowed_algs = c("RS256", "RS384", "RS512", "ES256", "ES384", "ES512",
"Ed25519", "EdDSA"),
allowed_token_types = c("Bearer"),
leeway = getOption("shinyOAuth.leeway", 30),
id_token_at_hash_required = FALSE,
infer_oidc_from_issuer = TRUE,
jwks_uri = NA_character_,
userinfo_allowed_algs = NULL,
jarm_signing_alg_values_supported = character(),
jarm_encryption_alg_values_supported = character(),
jarm_encryption_enc_values_supported = character(),
jarm_tolerate_duplicate_top_level_iss = FALSE,
endpoint_auth_metadata = list(),
...,
allowed_algs = NULL,
allow_missing_token_type = FALSE
)
Arguments
name |
Provider name (e.g., "github", "google"). Cosmetic only; used in logging and audit events |
auth_url |
URL of the provider's login and permission page. |
token_url |
URL where R exchanges the returned code for tokens. |
userinfo_url |
User info endpoint URL (optional) |
introspection_url |
Optional URL where the provider can confirm whether a token is still active (RFC 7662). |
revocation_url |
Optional URL where the app can ask the provider to invalidate a token, for example during logout (RFC 7009). |
par_url |
Optional Pushed Authorization Request (PAR) URL (RFC 9126).
When set, shinyOAuth first sends the authorization request from server to
provider and then redirects the browser with the returned |
par_required |
Logical. Whether the provider
requires authorization requests to be sent via PAR. When |
authorization_request_front_channel_mode |
Character scalar controlling
which browser-visible outer parameters shinyOAuth keeps when the actual
authorization request is carried by JAR or PAR. Use |
request_object_signing_alg_values_supported |
Optional vector of JWS
algorithms that the provider advertises for signed Request Objects (RFC
9101). This is mainly used for early validation when an OAuthClient
sends |
request_object_encryption_alg_values_supported |
Optional vector of JWE key-management algorithms that the provider advertises for encrypted Request Objects. This metadata is used for early validation when an OAuthClient enables Request Object encryption. |
request_object_encryption_enc_values_supported |
Optional vector of JWE content-encryption algorithms that the provider advertises for encrypted Request Objects. This metadata is used for early validation when an OAuthClient enables Request Object encryption. |
request_object_encryption_jwk |
Optional explicit recipient public key used to encrypt Request Objects when discovery-backed JWKS selection is not available or when you need to pin one specific encryption key. Accepts an OpenSSL public key, a PEM public-key string, a parsed JWK object, or a JWK JSON string. |
signed_request_object_required |
Logical. Whether the provider requires
signed Request Objects for authorization requests. When |
request_parameter_supported |
Logical or |
request_uri_parameter_supported |
Logical or |
request_uri_registration_required |
Logical or |
token_endpoint_auth_signing_alg_values_supported |
Optional vector of
JWS algorithms that the provider advertises for JWT-based client
authentication ( |
dpop_signing_alg_values_supported |
Optional vector of JWS algorithms
that the provider advertises for DPoP proof JWTs (RFC 9449). This
metadata is used for early validation of |
authorization_response_iss_parameter_supported |
Logical. Whether the
provider advertises RFC 9207 support for returning an |
response_modes_supported |
Optional character vector of OAuth/OIDC
|
mtls_endpoint_aliases |
Optional named list of RFC 8705 mTLS endpoint
aliases. Names should follow the metadata keys such as |
mtls_client_certificate_bound_access_tokens |
Logical. Whether the
authorization server advertises RFC 8705 capability to issue
certificate-bound access tokens. This describes server capability; the
client still has to opt into mTLS separately. When |
issuer |
Optional authorization-server issuer URL. You need this for issuer validation and features such as ID-token validation. shinyOAuth uses it to verify issuer claims and locate signing keys (JWKS), typically through an OIDC discovery document. |
issuer_match |
Character scalar controlling how strictly the discovery
document's
In most cases, keep the default |
use_nonce |
Whether to tie the ID token to this login using a random nonce. Keep enabled for OIDC. The nonce is sent in the request and checked in the returned ID token. |
use_pkce |
Whether to protect the code exchange using Proof Key for Code
Exchange (PKCE). Leave enabled; public clients require it.
It sends a |
pkce_method |
PKCE code challenge method ("S256" or "plain"). "S256" is recommended. Use "plain" only if you are working with a provider that does not support "S256". |
userinfo_required |
Whether to fetch a user profile after token exchange.
The result is stored in |
userinfo_id_token_match |
Whether fetched userinfo requires a validated ID
token for comparison. When both are available, their actual |
userinfo_signed_jwt_required |
Whether to require the user profile to arrive
as a signed JWT ( |
userinfo_id_selector |
A function that extracts the user ID from the userinfo response. Should take a single argument (the userinfo list) and return the user ID as a string. This is used for helpers that need a provider-specific application user
identifier, such as audit fields. It does not replace OIDC subject binding:
when a validated ID token and UserInfo are both available, their actual
|
id_token_required |
Whether to require an ID token to be returned
during token exchange. If no ID token is returned, the token exchange
will fail. This only makes sense for OpenID Connect providers and may
require the client's scope to include Both the S7 constructor and |
id_token_validation |
Whether to perform ID token validation after token exchange.
This requires the provider to be a valid OpenID Connect provider with a configured
Both the S7 constructor and |
extra_auth_params |
Extra parameters for authorization URL |
extra_token_params |
Extra parameters for token exchange.
|
extra_token_headers |
Extra headers for back-channel token-style
requests (named character vector), applied only to token exchange and
refresh. Configure |
token_auth_style |
How the client authenticates at the token endpoint. One of:
|
jwks_cache |
Storage for the provider's public signing keys. Defaults to
|
jwks_pins |
Optional character vector of RFC 7638 JWK thumbprints
(base64url) to pin against. If non-empty, fetched JWKS must contain keys
whose thumbprints match these values depending on |
jwks_pin_mode |
Pinning policy when |
jwks_host_issuer_match |
When TRUE, enforce that the discovery |
jwks_host_allow_only |
Optional explicit hostname that the jwks_uri must match.
When provided, jwks_uri host must equal this value (exact match). You can
pass either just the host (e.g., "www.googleapis.com") or a full URL; only
the host component will be used. If you need to include a port or an IPv6
literal, pass a full URL (e.g., |
id_token_allowed_algs |
Optional vector of allowed JWT algorithms for ID tokens.
Use to restrict acceptable |
allowed_token_types |
Character vector of acceptable OAuth token types
returned by the token endpoint (case-insensitive). Successful token
responses must include |
leeway |
Clock skew leeway (seconds) applied to ID token |
id_token_at_hash_required |
Whether to require the |
infer_oidc_from_issuer |
Whether setting |
jwks_uri |
Optional URL of the provider's public signing keys (JWKS). Normally these are located through OIDC discovery. Set this for manual key configuration, including OAuth-only JARM providers. |
userinfo_allowed_algs |
Optional signing algorithm allowlist for UserInfo
JWTs. |
jarm_signing_alg_values_supported |
Optional vector of JWS algorithms that the provider advertises for signed JWT Secured Authorization Responses (JARM). |
jarm_encryption_alg_values_supported |
Optional vector of JWE key-management algorithms that the provider advertises for encrypted JARM responses. |
jarm_encryption_enc_values_supported |
Optional vector of JWE content-encryption algorithms that the provider advertises for encrypted JARM responses. |
jarm_tolerate_duplicate_top_level_iss |
Logical. Whether shinyOAuth
should tolerate repeated identical top-level |
endpoint_auth_metadata |
Named list of independent |
... |
Deprecated renamed arguments accepted temporarily for backward compatibility. |
allowed_algs |
Compatibility alias for |
allow_missing_token_type |
Logical, default |
Details
Supply name, auth_url, and token_url to start. Add userinfo_url to
fetch profiles. Supplying issuer enables OIDC defaults, including ID token
validation, unless infer_oidc_from_issuer = FALSE. Advanced arguments must match
your provider's capabilities; see the advanced security vignette.
Value
OAuthProvider object
Examples
# Configure generic OAuth 2.0 provider (no OIDC)
generic_provider <- oauth_provider(
name = "example",
auth_url = "https://example.com/oauth/authorize",
token_url = "https://example.com/oauth/token",
# Optional URL for fetching user info:
userinfo_url = "https://example.com/oauth/userinfo"
)
# Configure generic OIDC provider manually
# (This defaults to using nonce & ID token validation)
generic_oidc_provider <- oauth_provider_oidc(
name = "My OIDC",
base_url = "https://my-issuer.example.com"
)
# Configure a OIDC provider via OIDC discovery
# (requires network access)
if (interactive()) {
# Using Auth0 sample issuer as an example
oidc_discovery_provider <- oauth_provider_oidc_discover(
issuer = "https://samples.auth0.com"
)
}
# GitHub preconfigured provider
github_provider <- oauth_provider_github()
# Google preconfigured provider
google_provider <- oauth_provider_google()
# Microsoft preconfigured provider
# For a complete app using a custom tenant ID, see:
# https://lukakoning.github.io/shinyOAuth/reference/oauth_provider_microsoft.html
# Spotify preconfigured provider
spotify_provider <- oauth_provider_spotify()
# Slack via OIDC discovery
# (requires network access)
if (interactive()) {
slack_provider <- oauth_provider_slack()
}
# Keycloak
# (requires configured Keycloak realm; example below is therefore not run)
if (interactive()) {
options(shinyOAuth.allow_insecure_oidc_loopback = TRUE)
oauth_provider_keycloak(base_url = "http://localhost:8080", realm = "myrealm")
}
# Auth0
# (requires configured Auth0 domain; example below is therefore not run)
if (interactive()) {
oauth_provider_auth0(domain = "your-tenant.auth0.com")
}
# Okta
# (requires configured Okta domain; example below is therefore not run)
if (interactive()) {
oauth_provider_okta(domain = "dev-123456.okta.com")
}
Create an Apple OAuthProvider
Description
Look up Apple's login settings and return an OAuthProvider for use with
oauth_client(). This helper makes an OIDC discovery request during setup.
Usage
oauth_provider_apple(name = "apple")
Arguments
name |
Optional provider name (default "apple") |
Details
Configure your client with:
Your Apple Services ID or App ID as
client_id.A client secret created with
oauth_client_secret_apple()using your Apple developer key.An HTTPS return address with a domain name; Apple does not accept localhost or IP addresses.
-
response_mode = "form_post"andoauth_form_post_ui()when requestingemailorname.
Read identity information from the validated ID token's claims. Apple has
no userinfo endpoint, so userinfo_required is FALSE. The one-time
user payload that Apple may send with a form POST callback is not mapped
into token@userinfo; do not rely on this helper to retrieve that payload.
Apple's documented email_verified strings ("true" and "false") are
normalized to logical values in validated claims after signature and issuer
verification. Other providers retain the standard JSON Boolean requirement.
Value
OAuthProvider object configured for Sign in with Apple
Examples
# Sign in with Apple requires an Apple Services ID, Team ID, key ID, and the
# corresponding P-256 private key. Network access is required for discovery.
if (interactive()) {
apple_provider <- oauth_provider_apple()
apple_secret <- oauth_client_secret_apple(
client_id = "com.example.web",
team_id = "ABCDEFGHIJ",
key_id = "ABC123DEFG",
private_key = openssl::read_key("AuthKey_ABC123DEFG.p8")
)
apple_client <- oauth_client(
provider = apple_provider,
client_id = "com.example.web",
client_secret = apple_secret,
redirect_uri = "https://example.com/oauth/callback",
scopes = c("openid", "email", "name"),
response_mode = "form_post"
)
}
Create an Auth0 OAuthProvider (via OIDC discovery)
Description
Look up login settings for your Auth0 domain. Pass the result to
oauth_client() with your registered app credentials. This helper makes
a discovery request during setup.
Usage
oauth_provider_auth0(domain, name = "auth0", audience = NULL)
Arguments
domain |
Your Auth0 domain, e.g., "your-domain.auth0.com" |
name |
Optional provider name (default "auth0") |
audience |
Optional audience value to send in authorization requests. |
Value
OAuthProvider object configured for the specified Auth0 domain
Examples
## Not run:
oauth_provider_auth0("your-domain.auth0.com")
## End(Not run)
Create a GitHub OAuthProvider
Description
Create the provider configuration for a GitHub OAuth App, then pass it with
your app credentials to oauth_client(). This configures profile retrieval
from GitHub's API; GitHub does not return an OIDC ID token.
Usage
oauth_provider_github(name = "github")
Arguments
name |
Optional provider name (default "github") |
Details
You can register a new GitHub OAuth 2.0 app in your OAuth App settings.
Value
OAuthProvider object for use with a GitHub OAuth 2.0 app
Examples
oauth_provider_github()
Create a Google OAuthProvider
Description
Use your Google app registration with oauth_client() to add Google
sign-in. The helper configures OIDC validation and profile retrieval.
Usage
oauth_provider_google(name = "google")
Arguments
name |
Optional provider name (default "google") |
Details
You can register a new Google OAuth 2.0 app in the Google Cloud Console. Configure the client ID & secret in your OAuthClient.
This preset uses a restricted Google OIDC profile: ID tokens must have
iss = "https://accounts.google.com", matching Google's discovery issuer.
Google's ID token validation guidance
also permits "accounts.google.com"; this alternate issuer is not accepted
by this preset. Issuer comparison remains exact, as for other OIDC providers.
Value
OAuthProvider object for use with a Google OAuth 2.0 app
Examples
oauth_provider_google()
Create a Keycloak OAuthProvider (via OIDC discovery)
Description
Look up login settings for a Keycloak realm. Supply your server URL
and realm name, then pass the result to oauth_client(). This helper
contacts the Keycloak server during setup.
Usage
oauth_provider_keycloak(
base_url,
realm,
name = paste0("keycloak-", realm),
token_auth_style = "body",
jarm_tolerate_duplicate_top_level_iss = TRUE
)
Arguments
base_url |
Base URL of the Keycloak server, e.g.,
"http://localhost:8080". Local HTTP development also requires
|
realm |
Keycloak realm name, e.g., "myrealm" |
name |
Optional provider name. Defaults to |
token_auth_style |
Optional override for token endpoint authentication
method. One of "header" (client_secret_basic), "body"
(client_secret_post), "public" (send |
jarm_tolerate_duplicate_top_level_iss |
Logical. Defaults to |
Value
OAuthProvider object configured for the specified Keycloak realm
Examples
## Not run:
oauth_provider_keycloak("https://login.example.com", realm = "myrealm")
## End(Not run)
Create a Microsoft (Entra ID) OAuthProvider
Description
Create provider settings for Microsoft Entra ID. Choose which accounts may
sign in with tenant, then pass the provider and your own registered app's
credentials to oauth_client().
Usage
oauth_provider_microsoft(
name = "microsoft",
tenant = c("common", "organizations", "consumers"),
id_token_validation = NULL
)
Arguments
name |
Optional friendly name for the provider. Defaults to "microsoft" |
tenant |
Tenant identifier ("common", "organizations", "consumers", or directory GUID). Defaults to "common" |
id_token_validation |
Optional override (logical). If |
Details
Use a directory (tenant) ID to target one organization. "organizations"
allows work or school accounts, "consumers" allows personal Microsoft
accounts, and "common" allows both. Your app registration and app access
rules must also permit the intended accounts.
ID token validation is enabled for these tenant choices. For a directory ID,
the issuer must match that directory. "common" and "organizations" use
Microsoft's tenant-independent issuer template and signing-key issuer rules.
"consumers" uses the consumer tenant issuer. The helper restricts ID token
algorithms to RS256 and fetches userinfo from Microsoft Graph.
Setting id_token_validation = FALSE disables ID token and nonce checks and
leaves OAuth plus profile retrieval. Keep the default for OIDC sign-in.
Tenant domains and other unrecognized tenant identifiers require this
explicit opt-out; otherwise use the directory GUID to retain OIDC validation.
Value
OAuthProvider object configured for Microsoft identity platform
Examples
if (
# Example requires configured Microsoft Entra ID (Azure AD) tenant:
nzchar(Sys.getenv("MS_TENANT")) &&
interactive() &&
requireNamespace("later", quietly = TRUE)
) {
library(shiny)
library(shinyOAuth)
# Configure provider and client (Microsoft Entra ID with your tenant)
client <- oauth_client(
provider = oauth_provider_microsoft(
# Provide your own tenant ID here (set as environment variable MS_TENANT)
tenant = Sys.getenv("MS_TENANT")
),
# Azure CLI public-client app ID; the tenant must permit this app.
# For your deployed app, use your own registration and redirect URI:
client_id = "04b07795-8ddb-461a-bbee-02f9e1bf7b46",
client_secret = "",
redirect_uri = "http://localhost:8100",
scopes = c("openid", "profile", "email")
)
# UI
ui <- oauth_ui(
fluidPage(
h3("OAuth demo (Microsoft Entra ID)"),
uiOutput("oauth_error"),
tags[["hr"]](),
h4("Auth object (summary)"),
verbatimTextOutput("auth_print"),
tags[["hr"]](),
h4("User info"),
verbatimTextOutput("user_info")
),
id = "auth",
client = client
)
# Server
server <- function(input, output, session) {
auth <- oauth_module_server("auth", client)
output[["auth_print"]] <- renderText({
authenticated <- auth[["authenticated"]]
tok <- auth[["token"]]
err <- auth[["error"]]
paste0(
"Authenticated?",
if (isTRUE(authenticated)) " YES" else " NO",
"\n",
"Has token? ",
if (!is.null(tok)) "YES" else "NO",
"\n",
"Has error? ",
if (!is.null(err)) "YES" else "NO",
"\n\n",
"Token present: ",
!is.null(tok),
"\n",
"Has refresh token: ",
!is.null(tok) && isTRUE(nzchar(tok@refresh_token)),
"\n",
"Has ID token: ",
!is.null(tok) && !is.na(tok@id_token),
"\n",
"Expires at: ",
if (!is.null(tok)) tok@expires_at else "N/A"
)
})
output[["user_info"]] <- renderPrint({
req(auth[["authenticated"]])
auth[["token"]]@userinfo
})
observeEvent(
list(auth[["error"]], auth[["error_description"]]),
{
if (interactive() && !is.null(auth[["error_description"]])) {
rlang::inform(c(
"OAuth error details",
"i" = paste0("error: ", auth[["error"]]),
"i" = paste0("error_description: ", auth[["error_description"]])
))
}
},
ignoreInit = TRUE
)
output[["oauth_error"]] <- renderUI({
if (is.null(auth[["error"]])) {
return(NULL)
}
msg <- if (identical(auth[["error"]], "access_denied")) {
"Sign-in was canceled or denied. Please try again."
} else {
"Authentication failed. Please try again."
}
div(class = "alert alert-danger", role = "alert", msg)
})
}
# Need to open app in 'localhost:8100' to match with redirect_uri
# of the public Azure CLI app (above). Browser must use 'localhost'
# too to properly set the browser cookie. But Shiny only redirects to
# '127.0.0.1' & blocks process once it runs. So we disable browser
# launch by Shiny & then use 'later::later()' to open the browser
# ourselves a short moment after the app starts
later::later(
function() {
utils::browseURL("http://localhost:8100")
},
delay = 0.25
)
# Run app
runApp(shinyApp(ui, server), port = 8100, launch.browser = FALSE)
}
Create a generic OpenID Connect (OIDC) OAuthProvider
Description
Build OIDC provider URLs from a base address and known endpoint paths.
Use this when configuring an OIDC service without discovery, with its
endpoint paths available from the service configuration or documentation.
Use oauth_provider_oidc_discover() if your provider offers discovery, which
looks up its actual URLs. This helper is for manual configuration; its
default paths must match the service you are using.
Usage
oauth_provider_oidc(
name,
base_url,
auth_path = "/authorize",
token_path = "/token",
userinfo_path = "/userinfo",
introspection_path = "/introspect",
use_nonce = TRUE,
id_token_validation = TRUE,
jwks_host_issuer_match = TRUE,
allowed_token_types = c("Bearer"),
...,
token_auth_style = "header"
)
Arguments
name |
Friendly name for the provider |
base_url |
Base URL for OIDC endpoints |
auth_path |
Authorization endpoint path (default: "/authorize") |
token_path |
Token endpoint path (default: "/token") |
userinfo_path |
User info endpoint path (default: "/userinfo") |
introspection_path |
Token introspection endpoint path (default: "/introspect") |
use_nonce |
Logical, whether to use OIDC nonce. Defaults to TRUE |
id_token_validation |
Logical, whether to validate ID tokens automatically for this provider. Defaults to TRUE |
jwks_host_issuer_match |
When TRUE (default), enforce that the JWKS host
discovered from the provider matches the issuer host exactly. For
providers that serve JWKS from a different host (e.g., Google), set
|
allowed_token_types |
Character vector of allowed token types for access tokens issued by this provider. Defaults to 'Bearer' |
... |
Additional arguments passed to |
token_auth_style |
Token endpoint client authentication style passed to
|
Value
OAuthProvider object
Examples
# Configure generic OAuth 2.0 provider (no OIDC)
generic_provider <- oauth_provider(
name = "example",
auth_url = "https://example.com/oauth/authorize",
token_url = "https://example.com/oauth/token",
# Optional URL for fetching user info:
userinfo_url = "https://example.com/oauth/userinfo"
)
# Configure generic OIDC provider manually
# (This defaults to using nonce & ID token validation)
generic_oidc_provider <- oauth_provider_oidc(
name = "My OIDC",
base_url = "https://my-issuer.example.com"
)
# Configure a OIDC provider via OIDC discovery
# (requires network access)
if (interactive()) {
# Using Auth0 sample issuer as an example
oidc_discovery_provider <- oauth_provider_oidc_discover(
issuer = "https://samples.auth0.com"
)
}
# GitHub preconfigured provider
github_provider <- oauth_provider_github()
# Google preconfigured provider
google_provider <- oauth_provider_google()
# Microsoft preconfigured provider
# For a complete app using a custom tenant ID, see:
# https://lukakoning.github.io/shinyOAuth/reference/oauth_provider_microsoft.html
# Spotify preconfigured provider
spotify_provider <- oauth_provider_spotify()
# Slack via OIDC discovery
# (requires network access)
if (interactive()) {
slack_provider <- oauth_provider_slack()
}
# Keycloak
# (requires configured Keycloak realm; example below is therefore not run)
if (interactive()) {
options(shinyOAuth.allow_insecure_oidc_loopback = TRUE)
oauth_provider_keycloak(base_url = "http://localhost:8080", realm = "myrealm")
}
# Auth0
# (requires configured Auth0 domain; example below is therefore not run)
if (interactive()) {
oauth_provider_auth0(domain = "your-tenant.auth0.com")
}
# Okta
# (requires configured Okta domain; example below is therefore not run)
if (interactive()) {
oauth_provider_okta(domain = "dev-123456.okta.com")
}
Discover and create an OpenID Connect (OIDC) OAuthProvider
Description
Supply your provider's issuer URL to create an OAuthProvider without
entering each service URL yourself. The helper downloads the provider's
discovery document (OpenID Connect Discovery) and enables OIDC login checks.
Pass the result to oauth_client().
Usage
oauth_provider_oidc_discover(
issuer,
name = NULL,
use_pkce = TRUE,
use_nonce = TRUE,
id_token_validation = TRUE,
token_auth_style = NULL,
id_token_allowed_algs = c("RS256", "RS384", "RS512", "ES256", "ES384", "ES512",
"Ed25519", "EdDSA"),
allowed_token_types = c("Bearer"),
jwks_host_issuer_match = TRUE,
issuer_match = c("url", "host", "none"),
...,
allowed_algs = NULL
)
Arguments
issuer |
The OIDC issuer base URL (including scheme), e.g.,
"https://login.example.com". The standard discovery-document URL ending
in |
name |
Optional friendly provider name. Defaults to the issuer hostname |
use_pkce |
Logical, whether to use PKCE for this provider. Defaults to TRUE. Public clients require PKCE. Setting FALSE also prevents automatic selection of public-client authentication; a confidential-client method must be available or explicitly configured instead. |
use_nonce |
Logical, whether to use OIDC nonce. Defaults to TRUE |
id_token_validation |
Logical, whether to validate ID tokens automatically for this provider. Defaults to TRUE |
token_auth_style |
Authentication style for token requests: "header"
(client_secret_basic), "body" (client_secret_post), or "public"
(public client; send |
id_token_allowed_algs |
Character vector of allowed ID token signing algorithms. Defaults to a broad set of common algorithms, including RSA (RS*), ECDSA (ES*), Ed25519, and legacy EdDSA. If the discovery document advertises supported algorithms, the intersection of advertised and caller-provided algorithms is used to avoid runtime mismatches. If there's no overlap, discovery fails with a configuration error (no fallback). |
allowed_token_types |
Character vector of allowed token types for access tokens issued by this provider. Defaults to 'Bearer' |
jwks_host_issuer_match |
When TRUE (default), enforce that the JWKS host
discovered from the provider matches the issuer host exactly. For
providers that serve JWKS from a different host, set
|
issuer_match |
Character scalar controlling how strictly to validate the
discovery document's
Prefer |
... |
Additional fields passed to |
allowed_algs |
Compatibility alias for |
Details
This function makes a network request. Call it once during app setup, outside
server(). Copy the issuer URL exactly from your provider's configuration,
including any trailing slash. A full discovery-document URL is also accepted.
Discovered token, UserInfo, introspection, and revocation endpoints are
copied into the provider when present. Discovering an introspection endpoint
does not itself require token introspection; set introspect = TRUE on
oauth_client() when login and refresh must perform that check.
Discovery describes what a service supports. Your app's registration may
require a particular token_auth_style, secret, or key; configure those to
match the registration. Automatic selection prefers "header", then "body",
then "public" when none is advertised and PKCE is enabled. For a public
client registration, set token_auth_style = "public" explicitly. JWT and
mTLS methods must also be selected explicitly.
Value
OAuthProvider object configured from discovery
Discovery validation
The discovered issuer must match the requested identifier by default.
Endpoints must use HTTPS. Host allowlisting does not permit HTTP: local OIDC
development requires options(shinyOAuth.allow_insecure_oidc_loopback = TRUE)
and a loopback host. options(shinyOAuth.allowed_hosts) can further restrict
endpoint hosts. Signing-key hosts have their own policy: by default they must
match the issuer host; use jwks_host_allow_only for a known different host.
The document must advertise the code flow (response_types_supported
includes "code"), non-empty subject_types_supported, RS256 in
id_token_signing_alg_values_supported, and a jwks_uri even when automatic
ID token validation is disabled. The permitted ID token algorithms are the
intersection of id_token_allowed_algs and the advertised algorithms; an empty
intersection is an error. Discovery keeps PKCE S256 and errors when the
provider explicitly excludes it, unless you select pkce_method = "plain".
Advanced metadata
Discovery also records capabilities for PAR, signed/encrypted requests,
JARM, DPoP, mTLS, and callback issuer identification. Client construction
checks the selected features against this metadata. See oauth_provider()
for individual fields and the advanced security vignette for setup.
When omitted by the provider, OIDC defaults apply: the JAR request
parameter is unsupported, request URIs are supported, request URI registration is not
required, and response modes are c("query", "fragment"). A mode still
has to be implemented by shinyOAuth to be usable. Caller-published request
URI registration must be arranged with the provider; discovery cannot check
your registration. PAR-issued handles do not need that client-hosted URI
registration.
Examples
# Configure generic OAuth 2.0 provider (no OIDC)
generic_provider <- oauth_provider(
name = "example",
auth_url = "https://example.com/oauth/authorize",
token_url = "https://example.com/oauth/token",
# Optional URL for fetching user info:
userinfo_url = "https://example.com/oauth/userinfo"
)
# Configure generic OIDC provider manually
# (This defaults to using nonce & ID token validation)
generic_oidc_provider <- oauth_provider_oidc(
name = "My OIDC",
base_url = "https://my-issuer.example.com"
)
# Configure a OIDC provider via OIDC discovery
# (requires network access)
if (interactive()) {
# Using Auth0 sample issuer as an example
oidc_discovery_provider <- oauth_provider_oidc_discover(
issuer = "https://samples.auth0.com"
)
}
# GitHub preconfigured provider
github_provider <- oauth_provider_github()
# Google preconfigured provider
google_provider <- oauth_provider_google()
# Microsoft preconfigured provider
# For a complete app using a custom tenant ID, see:
# https://lukakoning.github.io/shinyOAuth/reference/oauth_provider_microsoft.html
# Spotify preconfigured provider
spotify_provider <- oauth_provider_spotify()
# Slack via OIDC discovery
# (requires network access)
if (interactive()) {
slack_provider <- oauth_provider_slack()
}
# Keycloak
# (requires configured Keycloak realm; example below is therefore not run)
if (interactive()) {
options(shinyOAuth.allow_insecure_oidc_loopback = TRUE)
oauth_provider_keycloak(base_url = "http://localhost:8080", realm = "myrealm")
}
# Auth0
# (requires configured Auth0 domain; example below is therefore not run)
if (interactive()) {
oauth_provider_auth0(domain = "your-tenant.auth0.com")
}
# Okta
# (requires configured Okta domain; example below is therefore not run)
if (interactive()) {
oauth_provider_okta(domain = "dev-123456.okta.com")
}
Create an Okta OAuthProvider (via OIDC discovery)
Description
Look up login settings for your Okta domain and authorization server.
Pass the result to oauth_client() with your registered app credentials.
This helper makes a discovery request during setup.
Usage
oauth_provider_okta(domain, auth_server = "default", name = "okta")
Arguments
domain |
Your Okta domain, e.g., "dev-123456.okta.com" |
auth_server |
Authorization server ID for a custom authorization
server (default "default"). Use |
name |
Optional provider name (default "okta") |
Value
OAuthProvider object configured for the specified Okta domain
Examples
## Not run:
oauth_provider_okta("dev-123456.okta.com")
## End(Not run)
Create a Slack OAuthProvider (via OIDC discovery)
Description
Look up Slack's OpenID Connect settings for Sign in with Slack. This
helper contacts the discovery service during setup; pass its result to
oauth_client() with your Slack app credentials.
Usage
oauth_provider_slack(
name = "slack",
profile = c("confidential", "public_pkce")
)
Arguments
name |
Optional provider name (default "slack") |
profile |
Slack app registration profile: |
Value
OAuthProvider object configured for Slack
Examples
## Not run:
oauth_provider_slack()
## End(Not run)
Create a Spotify OAuthProvider
Description
Connect your app to a user's Spotify account. Pass this provider to
oauth_client() and request the scopes needed by the Spotify API calls you
plan to make. The helper configures profile retrieval through Spotify's API
and does not expect an ID token.
Usage
oauth_provider_spotify(name = "spotify", allow_legacy_id = FALSE)
Arguments
name |
Optional provider name (default "spotify") |
allow_legacy_id |
Whether to fall back to Spotify's mutable |
Details
Spotify requires scopes to be included in the authorization request.
Set requested scopes on the client with oauth_client(..., scopes = ...).
Identity uses Spotify's immutable account_id. Existing installations must
migrate stored account mappings and audit digests from id before upgrading.
Link the old and new identifiers only from a successfully authenticated
profile; do not use display names or email to merge accounts. To temporarily
preserve old mappings, explicitly replace provider@userinfo_id_selector
with function(userinfo) userinfo[["id"]] while completing the migration.
Value
OAuthProvider object for use with a Spotify OAuth 2.0 app
See Also
For a Shiny app that connects to Spotify to display the user's listening data, see the Spotify example.
Examples
oauth_provider_spotify()
Set up a Shiny UI for shinyOAuth
Description
Wrap your complete UI in oauth_ui() when using oauth_module_server().
It adds the browser code needed for login and protects callback responses
from caching and referrer disclosure. Supply id and client to accept
query callbacks before rendering any application UI or scripts.
Usage
oauth_ui(
base_ui,
id = NULL,
client = NULL,
request_uri_resolver = NULL,
clients = NULL
)
Arguments
base_ui |
Your app's complete UI, such as a |
id |
Shiny module ID, required with |
client |
OAuthClient used by the server module, required with |
request_uri_resolver |
Optional trusted public request URI resolver;
see |
clients |
Optional named list of OAuthClient objects keyed by module
ID, mutually exclusive with |
Details
Build the page as usual, for example with fluidPage(), then use
ui <- oauth_ui(ui, id = "auth", client = client), using the same module
ID and client as the server. Pass the result to shiny::shinyApp(). UI functions
are supported too, including functions accepting the Shiny request.
This wrapper includes use_shinyOAuth() setup.
With client, it also serves client-hosted Request Objects at the app root
using independent, single-use handles. Shared-worker apps need a shared
client@state_store with atomic take(); a memory store supports one process.
For response_mode = "form_post" or "form_post.jwt", use
oauth_form_post_ui() instead; it includes this setup and accepts POST
callbacks.
GET callbacks are validated and sealed into short-lived, single-use bridge
handles in the client's state store, then redirected to a clean URL before
application HTML is rendered. Logical state is consumed only after the
Shiny module verifies browser binding. The storage requirements and quotas
are the same as oauth_form_post_ui(). Register any fixed application query
parameters in client@redirect_uri; other inbound parameters are discarded.
For non-root callback paths use uiPattern = ".*" in shiny::shinyApp().
For multiple providers, supply clients = list(auth_a = client_a, auth_b = client_b) instead of id and client. Names are the server module
IDs. The registry accepts query and form-post callbacks on configured routes.
Each client must select a multi-server defense. Shared routes require
authorization_server_mode = "multi_issuer" and distinct trusted issuers.
An RFC 9207 iss or signed JARM issuer selects the configured client; the
complete callback is then verified before a bridge handle is stored.
Encrypted JARM on a shared route requires an outer iss identifying one
distinct configured issuer. The decrypted, signed response must match it.
Do not nest wrappers to route multiple providers.
Without id and client, ordinary pages still render, but raw OAuth GET
callbacks fail closed with a setup error. Earlier oauth_ui(ui) query-flow
applications must add those arguments. When integrating use_shinyOAuth()
directly, provide an equivalent dedicated callback endpoint: third-party or
application scripts must not execute on an unsanitized callback page.
HTML responses include Cache-Control: no-store, Pragma: no-cache, and
Referrer-Policy: no-referrer. The browser reads these headers
before loading page resources. The meta tag from use_shinyOAuth() takes
effect only once the browser reads that tag, so it may miss early resource
requests. You can also set the same HTTP header at your web server.
Value
A UI function to use as the ui argument to shiny::shinyApp().
See Also
oauth_module_server(), oauth_form_post_ui(), use_shinyOAuth()
Examples
ui <- oauth_ui(
shiny::fluidPage(
shiny::h2("My app"),
shiny::uiOutput("login")
)
)
# After creating your OAuth client, enable the callback bridge:
# ui <- oauth_ui(ui, id = "auth", client = client)
# Use this UI with your app's server function:
# shiny::shinyApp(ui = ui, server = server)
Alias for perform_resource_req()
Description
Deprecated alias for perform_resource_req().
Use perform_resource_req() for Bearer, DPoP, and mTLS-protected resource requests instead.
Usage
perform_client_bearer_req(
token,
url,
method = "GET",
headers = NULL,
query = NULL,
follow_redirect = FALSE,
check_url = TRUE,
client = NULL,
token_type = NULL,
dpop_nonce = NULL,
idempotent = NULL,
resource_hosts = NULL,
oauth_client = NULL
)
Arguments
token |
Either an OAuthToken object or a raw access token string. |
url |
Either the absolute URL to call or an |
method |
Optional HTTP method (character). Defaults to "GET". When
the effective token type is |
headers |
Optional named list or named character vector of extra
headers to set on the request. Header names are case-insensitive.
Any user-supplied |
query |
Optional named list of query parameters to append to the URL. |
follow_redirect |
Logical or |
check_url |
Logical. If |
client |
Optional OAuthClient. Required when the effective
token type is |
token_type |
Optional override for the access token type when |
dpop_nonce |
Optional DPoP nonce to embed in the proof for this
request. This is primarily useful after a resource server challenges with
|
idempotent |
Whether ordinary network/HTTP failures may be
retried safely. |
resource_hosts |
Optional non-empty character vector of trusted resource
host patterns, using |
oauth_client |
Compatibility alias for |
Value
Same value as perform_resource_req().
Call an API with an access token
Description
Send an authenticated API request on the user's behalf and return an
httr2 response. Pass the token from login and the API URL, then read the
response with httr2::resp_body_json() or another httr2 response helper.
Usage
perform_resource_req(
token,
url,
method = "GET",
headers = NULL,
query = NULL,
follow_redirect = FALSE,
check_url = TRUE,
client = NULL,
token_type = NULL,
dpop_nonce = NULL,
idempotent = NULL,
resource_hosts = NULL,
oauth_client = NULL
)
Arguments
token |
Either an OAuthToken object or a raw access token string. |
url |
Either the absolute URL to call or an |
method |
Optional HTTP method (character). Defaults to "GET". When
the effective token type is |
headers |
Optional named list or named character vector of extra
headers to set on the request. Header names are case-insensitive.
Any user-supplied |
query |
Optional named list of query parameters to append to the URL. |
follow_redirect |
Logical or |
check_url |
Logical. If |
client |
Optional OAuthClient. Required when the effective
token type is |
token_type |
Optional override for the access token type when |
dpop_nonce |
Optional DPoP nonce to embed in the proof for this
request. This is primarily useful after a resource server challenges with
|
idempotent |
Whether ordinary network/HTTP failures may be
retried safely. |
resource_hosts |
Optional non-empty character vector of trusted resource
host patterns, using |
oauth_client |
Compatibility alias for |
Details
Only send a token to an API you intend to authorize. The package applies its
URL policy, timeouts, and redirect defaults. It supports Bearer authentication
and tokens tied to a key (DPoP) or certificate (mTLS). For DPoP or mTLS,
also supply client so the request uses the matching key or
certificate.
Value
An httr2 response object.
Examples
# Make request using OAuthToken object
# (code is not run because it requires a real token from user interaction)
if (interactive()) {
# Inside reactive server code, after login has succeeded:
token <- auth[["token"]]
# Recommended for most callers: build + perform in one step.
response <- perform_resource_req(
token,
"https://api.example.com/resource",
query = list(limit = 5)
)
# Build only when you need to inspect the request yourself.
request <- resource_req(
token,
"https://api.example.com/resource",
query = list(limit = 5)
)
# Inspect request settings without printing authentication headers.
# httr2::req_perform(request) sends it when ready.
# Or start from your own httr2 request and still let shinyOAuth perform it
# so DPoP nonce retries remain available.
custom_request <- httr2::request("https://api.example.com/resource") |>
httr2::req_headers(Accept = "application/json") |>
httr2::req_url_query(limit = 5)
response <- perform_resource_req(token, custom_request)
# Constrain dynamic URLs before attaching a token. check_url alone does not
# restrict HTTPS hosts unless a global allowed_hosts policy is configured.
response <- perform_resource_req(
token,
input[["resource_url"]],
resource_hosts = "api.example.com",
follow_redirect = FALSE
)
}
Prepare a browser authorization request using GET or POST
Description
Creates the same one-use state, PKCE and nonce as prepare_call(), returning
a plain list describing how to send the request. Use this for applications
that handle their own browser navigation and handle_callback(). In Shiny,
the module's request_login() sends the request automatically.
Usage
prepare_authorization_request(
client,
browser_token,
request_uri_publisher = NULL
)
Arguments
client |
An OAuthClient object. |
browser_token |
Browser-bound token used to tie the login attempt to the current browser session. |
request_uri_publisher |
Optional function used when
|
Details
For GET, navigate the browser to url. For POST, create a form with url as
its action, method POST, and application/x-www-form-urlencoded encoding.
Add one hidden input per fields entry, assigning its name and value
through DOM properties or an HTML escaping library, then submit the form in
the current browser window. Repeated names (such as OAuth resource) must
remain repeated inputs. Do not send the authorization request from R: the
provider needs to interact with the user's browser and login cookies.
The client selects authorization_method = "POST" explicitly. Ordinary
OAuth providers may not support POST; confirm their documentation first.
smart_client() additionally requires the authorize-post capability.
The outgoing method is independent of the callback response_mode.
Configured PAR and Request Object requirements still apply. Each result
belongs to one login attempt; do not cache it or reuse it after logout.
POST preserves the authorization endpoint's fixed query and sends newly
composed fields in the body. It permits up to 256 fields and 128 KiB of
encoded form data; CR/LF and the browser-reserved _charset_ field are
rejected to prevent the browser changing field values. Existing state and
callback size limits also apply. Configure
your app's Content Security Policy form-action to allow the authorization
endpoint. Custom callers must preserve browser binding and callback handling.
Value
A list with method ("GET" or "POST"), url and fields.
fields is empty for GET; for POST it is a list of lists, each with scalar
character name and value. PAR expiry attributes are preserved as
documented in prepare_call(). The result contains transient authorization
data: do not log it or expose it to other browser sessions.
Examples
provider <- oauth_provider(
name = "Example service",
auth_url = "https://example.com/authorize",
token_url = "https://example.com/token",
token_auth_style = "public"
)
client <- oauth_client(
provider = provider,
client_id = "example-client",
redirect_uri = "http://127.0.0.1:8100/callback"
)
# In a custom browser flow, bind this secret to the initiating browser and
# supply it again to handle_callback(). Shiny modules manage this for you.
browser_token <- paste(format(openssl::rand_bytes(64)), collapse = "")
request <- prepare_authorization_request(client, browser_token)
request[["method"]]
Prepare an OAuth 2.0 authorization request and build its URL
Description
Prepare a login request and return the URL to open in the user's browser.
Use this when your application controls the browser redirect and callback
handling itself but needs shinyOAuth to construct the OAuth 2.0 authorization
request. Pair it with handle_callback() to complete the code flow.
Usage
prepare_call(
client,
browser_token,
request_uri_publisher = NULL,
oauth_client = NULL
)
Arguments
client |
An OAuthClient object. |
browser_token |
Browser-bound token used to tie the login attempt to the current browser session. |
request_uri_publisher |
Optional function used when
|
oauth_client |
Compatibility alias for |
Details
In a Shiny app using oauth_module_server(), call auth[["request_login"]]()
to start login through the module, which manages both operations and the
reactive session state.
The helper records one-time state and creates any required PKCE and nonce
values. Custom callers must preserve the browser binding and process the
returning callback themselves.
For an explicitly configured POST client, use prepare_authorization_request()
instead. This URL-only helper rejects POST before storing a transaction.
Value
A length-1 string containing the authorization URL to send the user
to. When PAR is used, the returned string also carries
shinyOAuth.par_request_uri, shinyOAuth.par_expires_in, and
shinyOAuth.par_expires_at attributes so callers can tell when the pushed
authorization request should be regenerated.
Examples
# Advanced example: your code supplies browser redirects and callback handling.
# For a Shiny app, oauth_module_server() manages these steps for you.
if (interactive()) {
# Define client
client <- oauth_client(
provider = oauth_provider_github(),
client_id = Sys.getenv("GITHUB_OAUTH_CLIENT_ID"),
client_secret = Sys.getenv("GITHUB_OAUTH_CLIENT_SECRET"),
redirect_uri = "http://127.0.0.1:8100"
)
# Get the login URL and store state in client's state store
# `<browser_token>` must be unpredictable and persisted for this transaction
# in storage bound to the application's exact origin (scheme, host, port).
# The module combines origin-scoped storage with an independent marker cookie
# and checks both on return. A cookie alone does not provide this boundary:
# cookies can be shared by applications on different ports of the same host.
# Shiny applications should use oauth_module_server() for the complete flow.
authorization_url <- prepare_call(client, "<browser_token>")
# Redirect user to authorization URL; retrieve code & state from the query;
# recover this transaction's `<browser_token>` through the origin-bound flow
# and verify its independent marker before calling handle_callback().
code <- "..."
state <- "..."
browser_token <- "..."
# Handle callback, exchanging code for token and validating state
token <- handle_callback(client, code, state, browser_token)
}
Refresh an OAuth 2.0 token
Description
Use a refresh token to obtain a new access token without sending the user
through login again. Call this when your application manages token lifetime
itself, for example before continuing API requests with an expiring token.
Assign the returned OAuthToken to keep the updated credentials.
oauth_module_server() can manage refresh during a Shiny session with
refresh_proactively = TRUE when a refresh token is available.
Usage
refresh_token(
client,
token,
async = FALSE,
introspect = NULL,
shiny_session = NULL,
oauth_client = NULL
)
Arguments
client |
OAuthClient object |
token |
OAuthToken object containing the refresh token |
async |
If |
introspect |
|
shiny_session |
Optional captured Shiny session details for audit events.
Normally supplied by the module; leave |
oauth_client |
Compatibility alias for |
Details
The provider may replace the refresh token too; otherwise the old refresh token is kept. Required userinfo is fetched again, and configured client introspection must succeed before the refreshed token is returned.
For ordinary OAuth clients, refresh explicitly requests the token's retained
granted_scopes when known. This preserves prior scope reductions and makes
an omitted response scope refer to that requested set. Tokens without known
scopes omit the request parameter. SMART uses its own original-grant rules.
OIDC refresh responses may omit the ID token, in which case the original is
kept. If a new ID token is returned, an original must be available and the
subject, issuer, and audience must remain consistent, as must auth_time
and nonce when applicable. Full signature and
claim validation runs when id_token_validation = TRUE. Userinfo is checked
against a validated ID token when both are available;
userinfo_id_token_match = TRUE requires that baseline.
Refresh does not establish a new interactive login. Use the module's
reauth_after_seconds argument when a fresh login is required.
A returned ID token must have an iat at or after the refresh request start,
allowing the provider's configured clock leeway and same-second issuance.
Within one R process, overlapping asynchronous calls for the same client and refresh token share one promise and result. Token snapshots, client settings, and validation options must match; conflicting calls fail before dispatch. A synchronous or reentrant call while that refresh is pending raises an error; await the existing promise instead. Separate R processes require coordination by the application. The input token is a value object: store the returned token for subsequent refreshes, and use an application generation check when assigning results after logout or a new login. Completed results are not cached.
Refresh errors carry a non-secret refresh_credential_outcome field:
"not_consumed", "consumed", "possibly_consumed", or "rejected".
Only "not_consumed" permits retrying the input refresh credential. After
any other outcome (including an unavailable worker result), discard that
credential and require a new login. This does not accept unvalidated access
or identity data. The module applies this rule even with indefinite sessions.
Value
An updated OAuthToken object with refreshed credentials.
What changes:
-
access_token: Always updated to the fresh token -
expires_at: Computed fromexpires_inwhen provided; otherwise a fallback lifetime set byshinyOAuth.default_expires_in(3600 seconds by default) -
refresh_token: Updated if the provider rotates it; otherwise preserved -
id_token: Updated only if the provider returns one (and it validates); otherwise the latest stored ID token is preserved -
original_id_token: Retained from login for continuity checks, even if intermediate refresh ID tokens omitnonceorauth_time -
userinfo: Refreshed ifuserinfo_required = TRUE; otherwise preserved -
extra_fields: Replaced by the additional parameters in the refresh response, or an empty list if none are returned. Not merged with earlier responses; explicit JSONnullvalues remain namedNULLentries. -
initial_extra_fields: Preserved from the initial code exchange. This historical snapshot does not establish current access permissions. -
cnf: Updated from the token response when present, and may be backfilled from refresh-time introspection when enabled. When the refresh response omits new observablecnf, shinyOAuth does not carry forward a priorx5t#S256thumbprint onto the refreshed token; mTLS sender-constrained state is kept only when the new token or its introspection response supplies freshcnf
Validation failures cause errors: If the provider returns a new ID
token that fails validation (wrong issuer, audience, expired, or subject
mismatch with original), or if userinfo subject doesn't match the new ID
token, the refresh fails with an error. In oauth_module_server(), this
clears the session and sets authenticated = FALSE, unless
indefinite_session = TRUE keeps it with token_stale = TRUE.
Examples
# get_userinfo(), introspect_token(), and refresh_token() are typically
# called by oauth_module_server() according to your provider/client and
# module settings, rather than directly by application code. The module
# also calls revoke_token() during logout when the provider supports it.
# These helpers are exported for custom login flows, on-demand profile or
# token checks, and applications that manage token lifetime themselves.
#
# The examples below require a real token from a completed login.
# Inside a reactive expression in server(), after creating auth with
# oauth_module_server() and confirming auth[["authenticated"]]:
if (interactive()) {
token <- auth[["token"]]
user_info <- get_userinfo(client, token)
# Requires an introspection endpoint. NA means activity is unknown.
result <- introspect_token(client, token)
isTRUE(result[["active"]])
# Requires a refresh token. Keep the returned replacement.
token <- refresh_token(client, token)
# Requires a revocation endpoint to invalidate the token at the provider.
result <- revoke_token(client, token, token_kind = "refresh")
}
Prepare an API request with an access token
Description
Build an httr2 request that uses the user's access token. Use this when
you want to inspect or customize a request before sending it. To build and
send in one step, use perform_resource_req().
Usage
resource_req(
token,
url,
method = "GET",
headers = NULL,
query = NULL,
follow_redirect = FALSE,
check_url = TRUE,
client = NULL,
token_type = NULL,
dpop_nonce = NULL,
resource_hosts = NULL,
oauth_client = NULL
)
Arguments
token |
Either an OAuthToken object or a raw access token string. |
url |
The absolute URL to call. |
method |
Optional HTTP method (character). Defaults to "GET". When
the effective token type is |
headers |
Optional named list or named character vector of extra
headers to set on the request. Header names are case-insensitive.
Any user-supplied |
query |
Optional named list of query parameters to append to the URL. |
follow_redirect |
Logical or |
check_url |
Logical. If |
client |
Optional OAuthClient. Required when the effective
token type is |
token_type |
Optional override for the access token type when |
dpop_nonce |
Optional DPoP nonce to embed in the proof for this
request. This is primarily useful after a resource server challenges with
|
resource_hosts |
Optional non-empty character vector of trusted resource
host patterns, using |
oauth_client |
Compatibility alias for |
Details
Only send a token to an API you intend to authorize. The package applies its
URL policy, timeouts, and redirect defaults. It supports Bearer authentication
and tokens tied to a key (DPoP) or certificate (mTLS). For DPoP or mTLS,
also supply client so the request uses the matching key or
certificate.
Managed Authorization credentials cannot be combined with an access_token
query parameter or form field. Inspection covers URL/query inputs and prebuilt
httr2 form, raw and string bodies labelled application/x-www-form-urlencoded.
JSON business fields are unaffected. File, multipart and streaming bodies are
not parsed; callers must ensure these contain no additional OAuth credential
transport. Later request changes outside these helpers require a new check.
Value
An httr2 request object, ready to be performed with
httr2::req_perform(). Callers may still add headers or query
parameters, but when the effective token type is DPoP they must not
change the request method or base URL after calling
resource_req() because the proof is already bound to those values.
DPoP note
DPoP proofs bind the current HTTP method and target URI (without query or
fragment). Use the query argument to preserve encoded resource paths;
external URL modifiers can decode reserved path characters. Changing the
method, scheme, host, or path invalidates the proof.
Examples
# Make request using OAuthToken object
# (code is not run because it requires a real token from user interaction)
if (interactive()) {
# Inside reactive server code, after login has succeeded:
token <- auth[["token"]]
# Recommended for most callers: build + perform in one step.
response <- perform_resource_req(
token,
"https://api.example.com/resource",
query = list(limit = 5)
)
# Build only when you need to inspect the request yourself.
request <- resource_req(
token,
"https://api.example.com/resource",
query = list(limit = 5)
)
# Inspect request settings without printing authentication headers.
# httr2::req_perform(request) sends it when ready.
# Or start from your own httr2 request and still let shinyOAuth perform it
# so DPoP nonce retries remain available.
custom_request <- httr2::request("https://api.example.com/resource") |>
httr2::req_headers(Accept = "application/json") |>
httr2::req_url_query(limit = 5)
response <- perform_resource_req(token, custom_request)
# Constrain dynamic URLs before attaching a token. check_url alone does not
# restrict HTTPS hosts unless a global allowed_hosts policy is configured.
response <- perform_resource_req(
token,
input[["resource_url"]],
resource_hosts = "api.example.com",
follow_redirect = FALSE
)
}
Revoke an OAuth 2.0 token
Description
Ask the provider to invalidate an access or refresh token, for example when
a user disconnects their account or your application disposes of stored
credentials. The provider must support token revocation. The Shiny module
calls this during logout; use auth[["logout"]]() to also clear its local session.
Revocation does not end the user's login session at the provider.
Usage
revoke_token(
client,
token,
token_kind = c("refresh", "access"),
async = FALSE,
shiny_session = NULL,
oauth_client = NULL,
oauth_token = NULL,
which = NULL
)
Arguments
client |
OAuthClient object |
token |
OAuthToken object containing tokens to revoke |
token_kind |
Which token to revoke: "refresh" (default) or "access" |
async |
If |
shiny_session |
Optional captured Shiny session details for audit events.
Normally supplied by the module; leave |
oauth_client |
Compatibility alias for |
oauth_token |
Compatibility alias for |
which |
Compatibility alias for |
Details
Uses the client's configured credentials and token_auth_style. Check the
returned status: an absent endpoint or token, or an unsuccessful HTTP
response, leaves the revocation result unknown. A successful response means
the provider accepted the request; local logout does not depend on it.
Value
A list with fields:
-
supported: logical,TRUEwhen a revocation endpoint is configured. -
revoked: logical orNA,TRUEwhen the provider accepted the revocation request,NAwhen revocation could not be attempted or the result is unknown. -
status: machine-readable status such as"ok","missing_token","revocation_unsupported", or"http_<code>".
Examples
# get_userinfo(), introspect_token(), and refresh_token() are typically
# called by oauth_module_server() according to your provider/client and
# module settings, rather than directly by application code. The module
# also calls revoke_token() during logout when the provider supports it.
# These helpers are exported for custom login flows, on-demand profile or
# token checks, and applications that manage token lifetime themselves.
#
# The examples below require a real token from a completed login.
# Inside a reactive expression in server(), after creating auth with
# oauth_module_server() and confirming auth[["authenticated"]]:
if (interactive()) {
token <- auth[["token"]]
user_info <- get_userinfo(client, token)
# Requires an introspection endpoint. NA means activity is unknown.
result <- introspect_token(client, token)
isTRUE(result[["active"]])
# Requires a refresh token. Keep the returned replacement.
token <- refresh_token(client, token)
# Requires a revocation endpoint to invalidate the token at the provider.
result <- revoke_token(client, token, token_kind = "refresh")
}
Configure a SMART on FHIR app registration
Description
Combine a reviewed smart_discover() snapshot with an existing app
registration. The result is an OAuthClient for oauth_connections(), with
the resource ID "fhir". Discovery does not register the app or grant access.
Usage
smart_client(
discovery,
client_id,
redirect_uri,
scopes,
required_scopes = scopes,
launch = c("standalone", "ehr"),
identity = c("none", "openid", "fhirUser"),
allow_v1_scopes = FALSE,
online_access_policy = c("online_only", "allow_offline"),
token_auth_style = c("public", "header", "private_key_jwt"),
client_secret = character(),
client_assertion_private_key = NULL,
client_assertion_private_key_kid = NULL,
client_assertion_alg = "RS384",
authorization_method = "GET",
response_mode = NULL,
authorization_server_mode = "single",
authorization_server_redirect_uris = character(),
initial_expires_in_fallback = NULL,
label = "FHIR server",
state_store = cachem::cache_mem(max_age = 300),
state_key = random_urlsafe(128),
state_payload_max_age = 300
)
Arguments
discovery |
A plain snapshot returned by |
client_id, redirect_uri |
App registration values. The callback must use HTTPS, except for the snapshot's explicit HTTP loopback development policy. |
scopes |
Permissions to request, without automatic wildcard/offline
access. Standalone patient scopes require |
required_scopes |
Minimum permissions, defaulting to |
launch |
|
identity |
|
allow_v1_scopes |
Explicit compatibility flag enabling |
online_access_policy |
|
token_auth_style |
Registration type: |
client_secret |
Secret for a symmetric registration; otherwise omit. |
client_assertion_private_key, client_assertion_private_key_kid |
Private
signing key and registered key ID for asymmetric authentication; see
|
client_assertion_alg |
|
authorization_method |
|
response_mode |
|
authorization_server_mode, authorization_server_redirect_uris |
See
|
initial_expires_in_fallback |
Optional positive lifetime in seconds, supplied by
the authorization server out of band for initial access tokens. Used only
when an initial response omits |
label |
Display label, default |
state_store, state_key, state_payload_max_age |
See |
Details
This constructor selects SMART 2.2 scope rules, S256 PKCE and the exact FHIR
base as the authorization request's aud. Identity is opt-in: "openid"
requests and requires openid, signed ID-token validation and nonce binding.
"fhirUser" additionally requests and requires the fhirUser scope and claim.
"none" does not enable OIDC because an issuer happens to be present.
A patient in context is independent of the authenticated user and local owner.
Only direct authorization requests and query/form POST callbacks are supported.
Claims requests (including auth_time), JAR, PAR, JARM, DPoP, mTLS and remote
freshness requirements are not supported by this constructor.
EHR clients require a fresh registered launch transaction.
Configure standalone and EHR registrations as separate clients when both are
needed. No launch handle is stored in shared provider configuration.
Local usability policy requires a positive lifetime. An initial response may
omit expires_in only when initial_expires_in_fallback is configured explicitly;
refresh responses must include it. The generic assumed lifetime is not used.
SMART back-channel and resource requests require TLS 1.2 or newer. A stronger
configured TLS minimum is preserved; ordinary clients keep their defaults.
Value
An OAuthClient, usable by the existing module or the separate
connection manager. @resource_bases contains the approved fhir base;
@required_scopes and @label use the ordinary client properties. @smart
describes the selected profile. The client contains registration settings,
never a user's token or launch context. Configure it outside server().
References
SMART 2.2 launch and client authentication.
Examples
## Not run:
site <- smart_discover("https://ehr.example/fhir/R4")
client <- smart_client(site, "registered-app", "https://app.example/callback",
scopes = c("launch/patient", "patient/Patient.r"),
required_scopes = "patient/Patient.r", token_auth_style = "public")
client@smart[["launch"]]
## End(Not run)
Read a connection's current SMART context
Description
Return interpreted patient/encounter and validated user references for the current accepted token. This is sensitive data; keep it out of general status tables and logs. Raw token extensions remain available separately on tokens.
Usage
smart_context(connection)
smart_patient(connection)
smart_fhir_user(connection)
Arguments
connection |
An OAuthConnection for a |
Details
smart_patient() fetches the contextual Patient only when the current grant
covers patient/Patient.r or user/Patient.r. smart_fhir_user() fetches the
identity reference only from a validated ID token. It accepts openid fhirUser,
a matching user read scope, or patient read permission when the identity is
the contextual Patient. Both stay inside the FHIR base and refuse redirects.
Both request JSON with Accept: application/fhir+json.
A foreign fhirUser reference is reported as context but is never fetched
with this connection's token. FHIR search, write, batch and pagination helpers
are outside these two convenience methods.
Patient IDs and user identity are different: the first identifies a chart,
the second the authenticated user. Neither changes the local connection owner.
General summaries omit all of this data. The application remains responsible
for displaying patient identity clearly and discarding data from an older
context revision. Experimental fhirContext and styling extensions remain
raw data and are not automatically fetched or interpreted.
Location-specific authorization_details apply scope, patient and encounter
overrides only for the configured FHIR base, with omitted fields falling back
to this response's top-level values. Other locations never add destinations.
Malformed details or multiple entries matching this base are rejected.
Value
smart_context() returns a list with version, fhir_base, revision,
changed, patient, encounter, fhirUser and need_patient_banner.
Absent values are NULL. revision increases when interpreted context
changes; key patient-dependent application data by connection ID and this
revision. Refresh omission carries context forward, except that a changed
or cleared patient clears an omitted encounter. Explicit null clears a
field, unless patient access still requires patient context. An initial
launch query never establishes this context.
smart_patient() and smart_fhir_user() return an httr2 response.
See Also
smart_client(), OAuthConnection
Examples
# Call these helpers with an authorized connection in its owning Shiny session.
# Reading the patient requires the corresponding patient or user read scope.
read_current_patient <- function(connection) {
context <- smart_context(connection)
if (is.null(context[["patient"]])) return(NULL)
httr2::resp_body_json(smart_patient(connection))
}
# The signed-in user can be different from the patient whose chart is open.
read_signed_in_user <- function(connection) {
httr2::resp_body_json(smart_fhir_user(connection))
}
Discover SMART on FHIR server metadata
Description
Read and validate the SMART App Launch STU 2.2 discovery document for a configured FHIR server. This returns server metadata for application setup; it does not register an app, choose client credentials, or start authorization.
Usage
smart_discover(fhir_base, endpoint_hosts = NULL, allow_http_loopback = FALSE)
Arguments
fhir_base |
Trusted FHIR base URL, including its complete path, for
example |
endpoint_hosts |
Character vector of exact permitted hostnames for
recognized metadata URLs. |
allow_http_loopback |
Logical, default |
Details
Call once during application setup, outside server(). The request appends
/.well-known/smart-configuration to the full FHIR base, removing its terminal
slash first. The supplied base and returned endpoint/issuer strings are
retained exactly, so discovery does not change protocol identifiers.
This is separate from oauth_provider_oidc_discover(). OAuth-only SMART
metadata need not contain OIDC issuer or signing-key information. Advertised
sso-openid-connect requires both issuer and jwks_uri; their presence
does not validate an identity or fetch OIDC metadata or keys.
Required SMART fields, conditional launch/SSO fields, and advertised
asymmetric-authentication metadata are checked. S256 must be advertised and
plain PKCE is rejected. Client-specific capability, scope, key and algorithm
selection belongs to the application's registration configuration.
scopes_supported is informative, not an exhaustive permission allowlist.
Its array may be empty. An empty token_endpoint_auth_methods_supported
array permits public registrations; confidential registrations still require
their selected method whenever that array is present.
Value
A plain named list with fhir_base (the supplied identifier),
discovery_url (the requested URL), smart_version ("2.2.0", the
validation baseline, not a detected server version), metadata (the parsed
document, with JSON arrays as lists), endpoint_hosts (the normalized
policy), and allow_http_loopback. No client, token, or live cache is stored
in the result. Invalid input or endpoint policy raises a
shinyOAuth_config_error; malformed metadata raises a
shinyOAuth_parse_error; failed HTTP requests raise a
shinyOAuth_http_error.
Network and trust policy
Supply a trusted, deployment-configured base, never an arbitrary browser query parameter. The base must be an absolute HTTPS URL without userinfo, query, fragment, or ambiguous path syntax. Discovered URLs must be absolute; this reader does not repair relative URLs from legacy servers.
endpoint_hosts applies to the issuer and these top-level URL fields when
present: authorization_endpoint, token_endpoint, jwks_uri,
registration_endpoint, management_endpoint, introspection_endpoint,
revocation_endpoint, userinfo_endpoint,
pushed_authorization_request_endpoint, smart_app_state_endpoint, and
user_access_brand_bundle. Matching uses exact hostnames, independently of
port. This is a discovery policy; it does not authorize resource requests.
The generic shinyOAuth.allowed_hosts option can further restrict these URLs.
The request carries no OAuth credentials. Redirects are refused even when the generic redirect option is enabled. Existing package TLS, timeout, response-size, and retry protections apply. JSON must be an object with no duplicate members; arrays and recognized URL fields have bounded validation. Errors do not include response bodies or returned metadata values.
Unknown extensions, including associated_endpoints, are retained as data
only. Their URLs are not fetched or approved for credential use. No automatic
metadata cache is used: every call reads a new snapshot. Applications should
review metadata changes before replacing configuration; do not rediscover
endpoints during a pending authorization or to reinterpret a retained grant.
References
SMART STU 2.2 discovery and asymmetric client metadata.
See Also
oauth_provider_oidc_discover(), smart_client()
Examples
## Not run:
site <- smart_discover(
"https://ehr.example/fhir/R4",
endpoint_hosts = c("ehr.example", "login.example")
)
site[["metadata"]][["token_endpoint"]]
site[["metadata"]][["capabilities"]]
## End(Not run)
Register a SMART EHR launch entry route
Description
Declare which approved SMART clients an EHR launch URL may select. Pass the
result in launch_routes to oauth_connections_ui(). The route is separate
from OAuth callbacks: iss means the FHIR base only here. Both iss and
launch are required, and callback parameters are rejected on this route.
Usage
smart_launch_route(path, client_names, max_age = 120)
Arguments
path |
Absolute application path, such as |
client_names |
Character vector of names from the manager's |
max_age |
Launch handoff lifetime in seconds, 30 to 300, default 120.
The handoff must be consumed and its authorization parameters prepared before
this deadline. Once prepared, login uses the client's |
Details
Initial entry is untrusted. It selects an already configured client by exact FHIR base, performs no discovery, and establishes no healthcare identity. A short-lived encrypted record binds the opaque launch handle to the browser owner and client. A clean continuation proves that owner before a fresh OAuth state/PKCE transaction can begin. Consumed handles cannot be reused to reconnect.
EHR entry supports top-level GET navigation with browser retention in one R
process. Account/session-only retention and iframe deployments are not supported
for EHR entry. Standalone launch supports all manager retention choices.
Up to eight unconsumed launch records are retained per browser owner across all
of a manager's launch routes, with a total limit of 1,000 per manager. Further
entries are rejected without evicting existing tickets. Consuming or expiring
a ticket releases its capacity.
Raw query size is limited to 8 KiB, launch handles to
2 KiB, and the only allowed initial parameters are iss and launch.
Apply ingress rate limits to both launch entry and ordinary pages that create browser owners. The owner quota isolates an existing browser's pending work; it is not a per-person or per-network-client rate limit, since unauthenticated callers can obtain additional browser owners. Configure these controls at a trusted reverse proxy using its verified client address, not arbitrary inbound forwarded headers. Size the owner registry for the admitted traffic window.
The HTTP response uses no-store and no-referrer policy and redirects to an opaque, owner-bound continuation. The package's external JavaScript removes that ticket from browser history before Shiny connects. The ticket alone cannot authorize a connection. No inline script permission or additional CSP nonce is needed for the handoff. Application access logs must also avoid recording raw launch query strings. Normal callback issuer checks and single-use browser/state checks still apply.
Value
A plain route configuration list, with no credentials or live state.
References
See Also
smart_client(), oauth_connections_ui()
Examples
## Not run:
# hospital is an EHR-mode SMART client; manager uses browser retention.
ui <- oauth_connections_ui(app_ui, "health", manager,
launch_routes = list(smart_launch_route("/smart/launch", "hospital")))
## End(Not run)
Add JavaScript dependency to the UI of a Shiny app
Description
Add shinyOAuth's JavaScript to a page so oauth_module_server() can redirect
the browser and manage its temporary login cookie. Use this inside an
existing fluidPage() or tagList() when you integrate the browser
dependency directly and configure response headers elsewhere, such as in
your web server or another UI wrapper.
Usage
use_shinyOAuth(inject_referrer_meta = TRUE)
Arguments
inject_referrer_meta |
If TRUE (default), adds a meta tag to the page:
an instruction asking the browser not to share the page's address when
loading images, scripts, or other files. Some files may start loading
before the browser reads this instruction. Use |
Details
oauth_ui() combines this browser setup with the HTTP header
Referrer-Policy: no-referrer, which prevents callback URLs from being
sent as referrers when page resources load. When using use_shinyOAuth()
directly, set that header in your HTTP response configuration for protection
from the start of page loading; the optional meta tag takes effect later.
oauth_ui() and oauth_form_post_ui() already include this dependency.
The dependency alone does not provide a callback bridge. Do not load
application or third-party scripts on raw OAuth callback pages; use
oauth_ui(ui, id, client) or a dedicated equivalent endpoint to redirect to
a clean URL before rendering the app. Callback responses must also send
Cache-Control: no-store and Pragma: no-cache.
Value
A tagList that loads the browser code once.
See Also
Examples
ui <- shiny::fluidPage(
use_shinyOAuth()
# ...
)