| Title: | Provider-Agnostic OAuth Authentication for 'shiny' Applications |
| Version: | 0.5.0 |
| Description: | Provides a simple, configurable, provider-agnostic 'OAuth 2.0' and 'OpenID Connect' (OIDC) authentication framework for '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, 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), 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: | testthat (≥ 3.0.0), knitr, rmarkdown, webfakes, promises, mirai (≥ 2.0.0), future, withr, later, callr, 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-05-23 06:48:41 UTC; dhrko |
| Author: | Luka Koning [aut, cre, cph] |
| Maintainer: | Luka Koning <koningluka@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-05-23 07:20:02 UTC |
shinyOAuth: Provider-Agnostic OAuth Authentication for 'shiny' Applications
Description
Provides a simple, configurable, provider-agnostic 'OAuth 2.0' and 'OpenID Connect' (OIDC) authentication framework for '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, 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
S7 class describing an OAuth 2.0 client configuration. It combines the provider, client credentials, redirect URI, requested scopes, and the state handling rules used during login and callback validation.
This is a low-level constructor intended for advanced use. Most users should
prefer the helper constructor oauth_client().
Usage
OAuthClient(
provider = NULL,
client_id = character(0),
client_secret = character(0),
client_private_key = NULL,
client_private_key_kid = NA_character_,
client_assertion_alg = NA_character_,
client_assertion_audience = NA_character_,
tls_client_cert_file = NA_character_,
tls_client_key_file = NA_character_,
tls_client_key_password = NA_character_,
tls_client_ca_file = NA_character_,
mtls_request_certificate_bound_access_tokens = FALSE,
authorization_request_mode = "parameters",
response_mode = NA_character_,
authorization_request_signing_alg = NA_character_,
authorization_request_audience = NA_character_,
authorization_request_encryption_alg = NA_character_,
authorization_request_encryption_enc = NA_character_,
authorization_request_encryption_kid = NA_character_,
authorization_request_ttl = 45,
authorization_request_nbf_skew = NA_real_,
dpop_private_key = NULL,
dpop_private_key_kid = NA_character_,
dpop_signing_alg = NA_character_,
dpop_require_access_token = FALSE,
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_temporal_claims = character(0),
required_acr_values = character(0),
introspect = FALSE,
introspect_elements = character(0)
)
Arguments
provider |
OAuthProvider object |
client_id |
OAuth client ID |
client_secret |
OAuth client secret. Validation rules:
Note: If your provider issues HS256 ID tokens and |
client_private_key |
Optional private key for |
client_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 |
tls_client_cert_file |
Optional path to the PEM-encoded client
certificate (or certificate chain) used for RFC 8705 mutual TLS client
authentication and certificate-bound protected-resource requests. Required
when |
tls_client_key_file |
Optional path to the PEM-encoded private key used
with |
tls_client_key_password |
Optional password used to decrypt an encrypted
PEM private key referenced by |
tls_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_request_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 |
authorization_request_mode |
Controls how the authorization request is transported to the provider.
Most users can keep the default. Request mode is an advanced option that
requires signing material on the client. shinyOAuth prefers
|
response_mode |
Authorization response mode for authorization-code
callbacks. Supported values are |
authorization_request_signing_alg |
Optional JWS algorithm override for
signed authorization requests when |
authorization_request_audience |
Optional override for the |
authorization_request_encryption_alg |
Optional JWE key-management
algorithm override for encrypted Request Objects. Current outbound support
is limited to |
authorization_request_encryption_enc |
Optional JWE content-encryption
algorithm override for encrypted Request Objects. Current outbound support
is limited to the AES-CBC-HMAC family ( |
authorization_request_encryption_kid |
Optional key identifier ( |
authorization_request_ttl |
Positive number of seconds to keep signed
authorization request objects ( |
authorization_request_nbf_skew |
Optional non-negative number of
seconds. When provided, shinyOAuth adds an |
dpop_private_key |
Optional private key used to generate DPoP proofs
(RFC 9449). Can be 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 |
Redirect URI registered with provider |
enforce_callback_issuer |
Logical or When |
scopes |
Vector of scopes to request. For OIDC providers (those with an
|
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 |
OIDC claims request parameter (OIDC Core §5.5). Allows requesting specific claims from the UserInfo Endpoint and/or in the ID Token. Can be:
|
state_store |
State storage backend. Defaults to Stored values must round-trip
The client automatically generates, persists (in |
state_payload_max_age |
Positive number of seconds. Maximum allowed age
for the decrypted state payload's This is the freshness window for the sealed Default is 300 seconds. |
state_entropy |
Integer. The length (in characters) of the randomly
generated state parameter. Higher values provide more entropy and better
security against CSRF attacks. Must be between 22 and 128 (to align with
|
state_key |
Optional per-client secret used as the state sealing key
for AES-GCM AEAD (authenticated encryption) of the state payload that
travels via the Type: character string (>= 32 bytes when encoded) or raw vector (>= 32 bytes). Raw keys enable direct use of high-entropy secrets from external stores. Both forms are normalized internally by cryptographic helpers. Multi-process deployments: if your app runs with multiple R workers or
behind a non-sticky load balancer, configure a 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 |
Controls validation of requested claims supplied via
the
If Enforceable requests under |
userinfo_jwt_required_temporal_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
Authentication Context Class Reference values (OIDC Core §2, §3.1.2.1).
When non-empty, the ID token returned by the provider must contain an
Additionally, when non-empty, the authorization request automatically
includes an Requires an OIDC-capable provider with |
introspect |
If TRUE, the login flow will call the provider's token
introspection endpoint (RFC 7662) to validate the access token. The login
is not considered complete unless introspection succeeds and returns
|
introspect_elements |
Optional character vector of additional
requirements to enforce on the introspection response when
|
Examples
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"
)
# 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 <- fluidPage(
use_shinyOAuth(),
uiOutput("login")
)
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 <- fluidPage(
use_shinyOAuth(),
actionButton("login_btn", "Login"),
uiOutput("login")
)
server_2 <- function(input, output, session) {
auth <- oauth_module_server(
"auth",
client,
auto_redirect = FALSE
)
observeEvent(input$login_btn, {
auth$request_login()
})
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 <- fluidPage(
use_shinyOAuth(),
uiOutput("ui")
)
server_3 <- function(input, output, session) {
auth <- oauth_module_server(
"auth",
client,
auto_redirect = TRUE
)
repositories <- reactiveVal(NULL)
observe({
req(auth$authenticated)
# Example additional API request using the access token
# (e.g., fetch user repositories from GitHub)
resp <- perform_resource_req(
auth$token,
"https://api.github.com/user/repos"
)
if (httr2::resp_is_error(resp)) {
repositories(NULL)
} else {
repos_data <- httr2::resp_body_json(resp, simplifyVector = TRUE)
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 (!is.null(repos)) {
tags$ul(
Map(
function(url, name) {
tags$li(tags$a(href = url, target = "_blank", 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
)
}
}
OAuthProvider S7 class
Description
S7 class describing an OAuth 2.0 or OpenID Connect provider. It stores the provider's endpoints and the rules shinyOAuth should follow during login, callback handling, token exchange, and optional OIDC checks.
This is a low-level constructor intended for advanced use. Most users should
prefer the helper constructors oauth_provider() for generic OAuth 2.0
providers or oauth_provider_oidc() / oauth_provider_oidc_discover() for
OpenID Connect providers. Those helpers enable secure defaults based on the
presence of an issuer and available endpoints.
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_,
require_pushed_authorization_requests = 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,
require_signed_request_object = FALSE,
request_parameter_supported = NA,
request_uri_parameter_supported = NA,
require_request_uri_registration = 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 = FALSE,
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 = FALSE,
id_token_validation = FALSE,
id_token_at_hash_required = FALSE,
extra_auth_params = list(),
extra_token_params = list(),
extra_token_headers = character(0),
mtls_endpoint_aliases = list(),
tls_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 = FALSE,
jwks_host_allow_only = NA_character_,
allowed_algs = c("RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"),
allowed_token_types = "Bearer",
leeway = getOption("shinyOAuth.leeway", 30)
)
Arguments
name |
Provider name (e.g., "github", "google"). Cosmetic only; used in logging and audit events |
auth_url |
Authorization endpoint URL |
token_url |
Token endpoint URL |
userinfo_url |
User info endpoint URL (optional) |
introspection_url |
Token introspection endpoint URL (optional; RFC 7662) |
revocation_url |
Token revocation endpoint URL (optional; 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 |
require_pushed_authorization_requests |
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. |
require_signed_request_object |
Logical. Whether the provider requires
signed Request Objects for authorization requests. When |
request_parameter_supported |
Logical or |
request_uri_parameter_supported |
Logical or |
require_request_uri_registration |
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 OIDC issuer URL. You need this when you want ID token
validation. shinyOAuth uses it to verify the ID token |
issuer_match |
Character scalar controlling how strictly the discovery
document's
In most cases, keep the default |
use_nonce |
Whether to use OIDC nonce. This adds a |
use_pkce |
Whether to use PKCE. This adds 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 userinfo after token exchange.
User information will be stored in the For the low-level constructor |
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 user identifier, such
as audit fields and UserInfo-to-ID-token subject matching. If you configure a
selector other than |
userinfo_id_token_match |
Whether to fail closed if UserInfo cannot be
bound to a validated ID token subject. Whenever both UserInfo and a
validated ID token are available, shinyOAuth compares the validated ID token
For |
userinfo_signed_jwt_required |
Whether to require that the userinfo
endpoint returns a signed JWT (
This prevents unsigned or weakly signed userinfo payloads from being treated
as trusted identity data. Requires Note: |
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 Note: At the S7 class level, this defaults to FALSE so that pure OAuth 2.0
providers can be configured without OIDC. Helper constructors like
|
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
Note: At the S7 class level, this defaults to FALSE. Helper constructors like
|
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). shinyOAuth applies these headers to token exchange, refresh, introspection, revocation, and PAR requests. Use this only for headers you intentionally want on that full set of authorization-server calls. |
mtls_endpoint_aliases |
Optional named list of RFC 8705 mTLS endpoint
aliases. Names should follow the metadata keys such as |
tls_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 |
Cache used for the provider's signing keys (JWKS). If not
provided, shinyOAuth creates an in-memory cache for 1 hour with
In most cases, a TTL between 15 minutes and 2 hours is reasonable. Shorter
TTLs pick up new keys faster but do more network work; longer TTLs reduce
traffic but may take longer to notice key rotation. If a new |
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., |
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 always include |
leeway |
Clock skew leeway (seconds) applied to ID token |
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
# See `?oauth_provider_microsoft` for example using a custom tenant ID
# 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()) {
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
S7 class representing OAuth tokens and (optionally) user information.
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
)
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 |
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.
Examples
# Please note: `get_userinfo()`, `introspect_token()`, and `refresh_token()`
# are typically not called by users of this package directly, but are called
# internally by `oauth_module_server()`. These functions are exported
# nonetheless for advanced use cases. Most users will not need to
# call these functions directly
# Example requires a real token from a completed OAuth flow
# (code is therefore not run; would error with placeholder values below)
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"
)
# Have a valid OAuthToken object; fake example below
# (typically provided by `oauth_module_server()` or `handle_callback()`)
token <- handle_callback(client, "<code>", "<payload>", "<browser_token>")
# Get userinfo
user_info <- get_userinfo(client, token)
# Introspect token (if supported by provider)
introspection <- introspect_token(client, token)
# Refresh token
new_token <- refresh_token(client, token, introspect = TRUE)
}
Alias for resource_req()
Description
Deprecated alias for resource_req() to avoid a breaking change in the public API.
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,
oauth_client = NULL,
token_type = NULL,
dpop_nonce = 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. If |
check_url |
Logical. If |
oauth_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
|
Value
Same value as resource_req().
Create a custom cache backend (cachem-like)
Description
Builds a small cachem-like backend object with methods compatible with what
shinyOAuth needs: $get(key, missing), $set(key, value), $remove(key),
and optional $info().
Use this helper when you want to plug a custom state store or JWKS cache
into shinyOAuth, when cachem::cache_mem() is not suitable (e.g.,
multi-process deployments with non-sticky workers).
In such cases, you may want to use a shared external cache (e.g., database,
Redis, Memcached).
The resulting object can be used in both places where shinyOAuth accepts a cache-like object:
OAuthClient@state_store (requires
$get,$set,$remove; optional$info)OAuthProvider@jwks_cache (requires
$get,$set; optional$remove,$info)
For OAuthClient@state_store, stored values are small lists. browser_token
must always round-trip as a non-empty string. pkce_code_verifier and
nonce are required only when the provider enables PKCE or nonce
validation; otherwise stores may preserve them as NULL or omit them when
serializing.
The $info() method is optional, but if provided and it returns a list with
max_age (seconds), shinyOAuth will align browser cookie max-age in
oauth_module_server() to that value.
Usage
custom_cache(get, set, remove, take = NULL, info = 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 |
Value
An R6 object exposing cachem-like $get/$set/$remove/$info methods
Examples
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)
},
# Atomic get-and-delete: preferred for state stores in multi-worker
# deployments to prevent TOCTOU replay attacks. For per-process caches
# (like cachem::cache_mem()) this is optional; for shared backends (Redis,
# database) it should map to the backend's atomic primitive (e.g., GETDEL).
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 = 600)
)
Throw an error if specific dev/debug softeners are enabled
Description
Deprecated helper that errors when a small subset of shinyOAuth's development and debugging softeners 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 -
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 any softening options that relax default safety
# protections are 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()
}
Get user info from OAuth 2.0 provider
Description
Fetches user information from the provider's userinfo endpoint using the supplied access token. Emits an audit event with redacted details. When a validated ID token baseline is available, or when provider policy requires one, this helper also enforces OIDC UserInfo subject binding before returning.
Usage
get_userinfo(oauth_client, token, token_type = NULL, shiny_session = NULL)
Arguments
oauth_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 pre-captured Shiny session context (from
|
Value
A list containing the user information returned by the provider.
Examples
# Please note: `get_userinfo()`, `introspect_token()`, and `refresh_token()`
# are typically not called by users of this package directly, but are called
# internally by `oauth_module_server()`. These functions are exported
# nonetheless for advanced use cases. Most users will not need to
# call these functions directly
# Example requires a real token from a completed OAuth flow
# (code is therefore not run; would error with placeholder values below)
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"
)
# Have a valid OAuthToken object; fake example below
# (typically provided by `oauth_module_server()` or `handle_callback()`)
token <- handle_callback(client, "<code>", "<payload>", "<browser_token>")
# Get userinfo
user_info <- get_userinfo(client, token)
# Introspect token (if supported by provider)
introspection <- introspect_token(client, token)
# Refresh token
new_token <- refresh_token(client, token, introspect = TRUE)
}
Handle OAuth 2.0 callback: verify state, swap code for token, verify token
Description
Completes the callback step of the login flow. It validates the callback state, exchanges the returned code for tokens, and verifies the result.
Usage
handle_callback(
oauth_client,
code,
payload,
browser_token,
shiny_session = NULL,
iss = NULL
)
Arguments
oauth_client |
An OAuthClient object. |
code |
Authorization code received from the provider. |
payload |
Encrypted state payload returned by the provider. 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 pre-captured Shiny session context (from
|
iss |
Optional RFC 9207 callback issuer ( |
Value
An OAuthToken object. If callback validation, token exchange, or token verification fails, the function raises an error.
Examples
# Please note: `prepare_call()` & `handle_callback()` are typically
# not called by users of this package directly, but are called
# internally by `oauth_module_server()`. These functions are exported
# nonetheless for advanced use cases. Most users will not need to
# call these functions directly
# Below code shows generic usage of `prepare_call()` and `handle_callback()`
# (code is not run because it would require user interaction)
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 authorization URL and and store state in client's state store
# `<browser_token>` is a token that identifies the browser session
# and would typically be stored in a browser cookie
# (`oauth_module_server()` handles this typically)
authorization_url <- prepare_call(client, "<browser_token>")
# Redirect user to authorization URL; retrieve code & payload from query;
# read also `<browser_token>` from browser cookie
# (`oauth_module_server()` handles this typically)
code <- "..."
payload <- "..."
browser_token <- "..."
# Handle callback, exchanging code for token and validating state
# (`oauth_module_server()` handles this typically)
token <- handle_callback(client, code, payload, browser_token)
}
Introspect an OAuth 2.0 token
Description
Introspects an access or refresh token when the provider exposes an introspection endpoint (RFC 7662). Returns a small result object describing whether introspection is supported and, when known, whether the token is active.
Authentication to the introspection endpoint mirrors the provider's
token_auth_style:
"header" (default): HTTP Basic with
client_id/client_secret."body": form fields
client_idand (when available)client_secret."public": form field
client_idonly;client_secretis never sent."client_secret_jwt" / "private_key_jwt": a signed JWT client assertion is generated (RFC 7523) and sent via
client_assertion_typeandclient_assertion, withaudresolved viaresolve_client_assertion_audience()(soclient_assertion_audienceoverrides are honored).
Usage
introspect_token(
oauth_client,
oauth_token,
which = c("access", "refresh"),
async = FALSE,
shiny_session = NULL
)
Arguments
oauth_client |
OAuthClient object |
oauth_token |
OAuthToken object to introspect |
which |
Which token to introspect: "access" (default) or "refresh". |
async |
Logical, default FALSE. If TRUE and an async backend is
configured, the operation is dispatched through shinyOAuth's async
promise path and this function returns a promise-compatible async result
that resolves to the result list. mirai is preferred when daemons are
configured via |
shiny_session |
Optional pre-captured Shiny session context (from
|
Details
Best-effort semantics:
If the provider does not expose an introspection endpoint, the function returns
supported = FALSE,active = NA, andstatus = "introspection_unsupported".If the endpoint responds with an HTTP error (e.g., 404/500) or the body cannot be parsed or does not include a usable
activefield, the function does not throw. It returnssupported = TRUE,active = NA, and a descriptivestatus(for example,"http_404","invalid_json","missing_active"). In this context,NAmeans "unknown" and will not break flows unless your code explicitly requires a definitive result (i.e.,isTRUE(result$active)).Providers vary in how they encode the RFC 7662
activefield (logical, numeric, or character variants like "true"/"false", 1/0). These are normalized to logicalTRUE/FALSEwhen possible; otherwiseactiveis set toNA.
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
# Please note: `get_userinfo()`, `introspect_token()`, and `refresh_token()`
# are typically not called by users of this package directly, but are called
# internally by `oauth_module_server()`. These functions are exported
# nonetheless for advanced use cases. Most users will not need to
# call these functions directly
# Example requires a real token from a completed OAuth flow
# (code is therefore not run; would error with placeholder values below)
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"
)
# Have a valid OAuthToken object; fake example below
# (typically provided by `oauth_module_server()` or `handle_callback()`)
token <- handle_callback(client, "<code>", "<payload>", "<browser_token>")
# Get userinfo
user_info <- get_userinfo(client, token)
# Introspect token (if supported by provider)
introspection <- introspect_token(client, token)
# Refresh token
new_token <- refresh_token(client, token, introspect = TRUE)
}
Check if URL(s) are HTTPS and/or in allowed hosts lists
Description
Returns TRUE if every input URL passes shinyOAuth's scheme and host
policy. In practice, each URL must be either:
a syntactically valid HTTPS URL, and (if set) whose host matches
allowed_hosts, oran HTTP URL whose host matches
allowed_non_https_hosts(e.g. localhost, 127.0.0.1, ::1), and (if set) also matchesallowed_hosts.
If the input omits the scheme (e.g., "localhost:8080/cb"), this function will first attempt to validate it as HTTP (useful for loopback development), and if that fails, as HTTPS. This mirrors how helpers normalize inputs for convenience while still enforcing the same host and scheme policies.
allowed_hosts is the allowlist of hosts or domains that are permitted,
while allowed_non_https_hosts defines which hosts are allowed to use HTTP
instead of HTTPS. If allowed_hosts is NULL or length 0, all hosts are
allowed subject to the scheme rules above.
Since allowed_hosts supports globs, a value like "*" matches any host
and therefore effectively disables endpoint host restrictions. Only use a catch-all
pattern when you truly intend to allow any host. In most deployments you should pin
to your expected domain(s), e.g. c(".example.com") or a specific host name.
Wildcards: allowed_hosts and allowed_non_https_hosts support globs:
* = any chars, ? = one char. A leading .example.com matches the
domain itself and any subdomain.
Any non-URLs, NAs, or empty strings cause a FALSE result.
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
This function is used internally to validate redirect URIs in OAuth clients,
but can also be used directly to test whether URLs would be accepted.
Internally, the defaults come from the options
shinyOAuth.allowed_non_https_hosts and shinyOAuth.allowed_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("*"))
Create generic OAuthClient
Description
Main helper for creating a validated OAuthClient configuration before
oauth_module_server() starts login or callback handling.
Usage
oauth_client(
provider,
client_id = Sys.getenv("OAUTH_CLIENT_ID"),
client_secret = Sys.getenv("OAUTH_CLIENT_SECRET"),
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_private_key = NULL,
client_private_key_kid = NULL,
client_assertion_alg = NULL,
client_assertion_audience = NULL,
tls_client_cert_file = NULL,
tls_client_key_file = NULL,
tls_client_key_password = NULL,
tls_client_ca_file = NULL,
mtls_request_certificate_bound_access_tokens = FALSE,
authorization_request_mode = c("parameters", "request", "request_uri"),
response_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 = 45,
authorization_request_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_temporal_claims = character(0),
required_acr_values = character(0),
introspect = FALSE,
introspect_elements = character(0)
)
Arguments
provider |
OAuthProvider object |
client_id |
OAuth client ID |
client_secret |
OAuth client secret. Validation rules:
Note: If your provider issues HS256 ID tokens and |
redirect_uri |
Redirect URI registered with provider |
enforce_callback_issuer |
Logical or When |
scopes |
Vector of scopes to request. For OIDC providers (those with an
|
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 |
OIDC claims request parameter (OIDC Core §5.5). Allows requesting specific claims from the UserInfo Endpoint and/or in the ID Token. Can be:
|
state_store |
State storage backend. Defaults to Stored values must round-trip
The client automatically generates, persists (in |
state_payload_max_age |
Positive number of seconds. Maximum allowed age
for the decrypted state payload's This is the freshness window for the sealed Default is 300 seconds. |
state_entropy |
Integer. The length (in characters) of the randomly
generated state parameter. Higher values provide more entropy and better
security against CSRF attacks. Must be between 22 and 128 (to align with
|
state_key |
Optional per-client secret used as the state sealing key
for AES-GCM AEAD (authenticated encryption) of the state payload that
travels via the Type: character string (>= 32 bytes when encoded) or raw vector (>= 32 bytes). Raw keys enable direct use of high-entropy secrets from external stores. Both forms are normalized internally by cryptographic helpers. Multi-process deployments: if your app runs with multiple R workers or
behind a non-sticky load balancer, configure a shared |
client_private_key |
Optional private key for |
client_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 |
tls_client_cert_file |
Optional path to the PEM-encoded client
certificate (or certificate chain) used for RFC 8705 mutual TLS client
authentication and certificate-bound protected-resource requests. Required
when |
tls_client_key_file |
Optional path to the PEM-encoded private key used
with |
tls_client_key_password |
Optional password used to decrypt an encrypted
PEM private key referenced by |
tls_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_request_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 |
authorization_request_mode |
Controls how the authorization request is transported to the provider.
Most users can keep the default. Request mode is an advanced option that
requires signing material on the client. shinyOAuth prefers
|
response_mode |
Authorization response mode for authorization-code
callbacks. Supported values are |
authorization_request_signing_alg |
Optional JWS algorithm override for
signed authorization requests when |
authorization_request_audience |
Optional override for the |
authorization_request_encryption_alg |
Optional JWE key-management
algorithm override for encrypted Request Objects. Current outbound support
is limited to |
authorization_request_encryption_enc |
Optional JWE content-encryption
algorithm override for encrypted Request Objects. Current outbound support
is limited to the AES-CBC-HMAC family ( |
authorization_request_encryption_kid |
Optional key identifier ( |
authorization_request_ttl |
Positive number of seconds to keep signed
authorization request objects ( |
authorization_request_nbf_skew |
Optional non-negative number of
seconds. When provided, shinyOAuth adds an |
dpop_private_key |
Optional private key used to generate DPoP proofs
(RFC 9449). Can be 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 |
Controls validation of requested claims supplied via
the
If Enforceable requests under |
userinfo_jwt_required_temporal_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
Authentication Context Class Reference values (OIDC Core §2, §3.1.2.1).
When non-empty, the ID token returned by the provider must contain an
Additionally, when non-empty, the authorization request automatically
includes an Requires an OIDC-capable provider with |
introspect |
If TRUE, the login flow will call the provider's token
introspection endpoint (RFC 7662) to validate the access token. The login
is not considered complete unless introspection succeeds and returns
|
introspect_elements |
Optional character vector of additional
requirements to enforce on the introspection response when
|
Value
OAuthClient object
Examples
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"
)
# 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 <- fluidPage(
use_shinyOAuth(),
uiOutput("login")
)
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 <- fluidPage(
use_shinyOAuth(),
actionButton("login_btn", "Login"),
uiOutput("login")
)
server_2 <- function(input, output, session) {
auth <- oauth_module_server(
"auth",
client,
auto_redirect = FALSE
)
observeEvent(input$login_btn, {
auth$request_login()
})
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 <- fluidPage(
use_shinyOAuth(),
uiOutput("ui")
)
server_3 <- function(input, output, session) {
auth <- oauth_module_server(
"auth",
client,
auto_redirect = TRUE
)
repositories <- reactiveVal(NULL)
observe({
req(auth$authenticated)
# Example additional API request using the access token
# (e.g., fetch user repositories from GitHub)
resp <- perform_resource_req(
auth$token,
"https://api.github.com/user/repos"
)
if (httr2::resp_is_error(resp)) {
repositories(NULL)
} else {
repos_data <- httr2::resp_body_json(resp, simplifyVector = TRUE)
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 (!is.null(repos)) {
tags$ul(
Map(
function(url, name) {
tags$li(tags$a(href = url, target = "_blank", 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
)
}
}
Build RFC 8705 mTLS registration metadata
Description
Returns a JSON-ready list of client metadata for registering an OAuthClient that uses RFC 8705 mutual TLS or requests certificate-bound access tokens.
For token_auth_style = "tls_client_auth", this helper returns
token_endpoint_auth_method = "tls_client_auth" plus exactly one RFC 8705
certificate identifier field:
tls_client_auth_subject_dn, tls_client_auth_san_dns,
tls_client_auth_san_uri, tls_client_auth_san_ip, or
tls_client_auth_san_email.
For token_auth_style = "self_signed_tls_client_auth", this helper returns
token_endpoint_auth_method = "self_signed_tls_client_auth" plus either an
inline jwks document built from the configured client certificate and
certificate chain (published via x5c), or a caller-supplied jwks_uri.
For clients that request RFC 8705 certificate-bound access tokens without
mTLS OAuth client authentication, this helper returns the runtime
token_auth_style mapped back to the dynamic-registration metadata value
(for example, public becomes none) and emits
tls_client_certificate_bound_access_tokens = TRUE.
This helper prepares metadata only. It does not make a registration HTTP call.
Usage
oauth_client_mtls_registration(
oauth_client,
tls_client_auth_type = c("subject_dn", "san_dns", "san_uri", "san_ip", "san_email"),
tls_client_auth_value = NULL,
jwks_uri = NULL
)
Arguments
oauth_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
|
Value
A JSON-ready list of RFC 7591/RFC 8705 client metadata.
Wrap a Shiny UI to enable OAuth 2.0/OIDC form_post callbacks
Description
oauth_form_post_ui() enables the OpenID Foundation OAuth 2.0 Form Post
Response Mode for apps that use oauth_module_server(). It wraps your
existing Shiny UI so a provider can POST an authorization response to the
app's redirect URI. The POST body is stored server-side under a short-lived
one-time handle, and the browser is redirected back to the app with only
that opaque handle in the query string.
For most apps, this helper is not needed because the default transport for authorization responses is the query string, which works without this UI wrapper. You only need to use this helper if your provider requires or strongly recommends form_post response mode.
To request form_post response mode from the provider, wrap your UI with this
helper, configure your OAuthClient with response_mode = "form_post", and
ensure the redirect_uri is set to a URL that routes to this UI wrapper
(e.g., the app's root URL or a specific callback path).
This helper handles the plain form_post response mode, where the POST body
contains authorization response parameters such as code, state, error,
and iss. It does not decode JWT Secured Authorization Response Mode (JARM)
responses such as response_mode = "form_post.jwt".
Usage
oauth_form_post_ui(base_ui, id, client, callback_path = 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 |
Details
When this wrapper is used, it also injects use_shinyOAuth() automatically
for the wrapped GET UI, so you do not need a separate top-level
use_shinyOAuth() call.
The server-side callback handle is single-use and is rejected if it is older
than the smaller of client@state_payload_max_age and the configured
state_store TTL. The raw POST body and transient handle query parameters
are also bounded by the shinyOAuth.callback_max_form_post_* options
described in the usage vignette.
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.
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)
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 & OIDC authentication module for Shiny applications
Description
This function implements a Shiny module server that manages OAuth 2.0/OIDC authentication for Shiny applications. It handles the OAuth 2.0/OIDC flow, including redirecting users to the authorization endpoint, securely processing the callback, exchanging authorization codes for tokens, verifying tokens, and managing token refresh. It also provides options for automatic or manual login flows, session expiry, and proactive token refresh.
Note: when using this module, you must include
shinyOAuth::use_shinyOAuth() in your UI definition to load the
necessary JavaScript dependencies.
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 = 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")
)
Arguments
id |
Shiny module id |
client |
OAuthClient object |
auto_redirect |
If TRUE (default), unauthenticated sessions will
immediately initiate the OAuth flow by redirecting the browser to the
authorization endpoint. If FALSE, the module will not auto-redirect;
instead, the returned object exposes helpers for triggering login
manually (use |
async |
If TRUE, dispatches token exchange and refresh through
shinyOAuth's async promise path and updates values when the promise
resolves. mirai is preferred when daemons are configured with
|
indefinite_session |
If TRUE, the module will not automatically clear
the token due to access-token expiry or the |
reauth_after_seconds |
Optional maximum session age in seconds. If set,
the module will remove the token (and thus set |
refresh_proactively |
If TRUE, will automatically refresh tokens
before they expire (if refresh token is available). The refresh is
scheduled adaptively so that it executes approximately at
|
refresh_lead_seconds |
Number of seconds before expiry to attempt proactive refresh (default: 60) |
refresh_check_interval |
Fallback check interval in milliseconds for expiry/refresh (default: 10000 ms). When expiry is known, the module uses adaptive scheduling to wake up exactly when needed; this interval is used as a safety net or when expiry is unknown/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 |
Optional cookie Path to scope the browser token
cookie. By default ( For apps deployed under nested routes or where the OAuth callback may land on a different route than the initial page, keeping the default (root path) ensures the browser token cookie is available and clearable across app routes. If you deliberately scope the cookie to a sub-path, make sure all relevant routes share that prefix. |
browser_cookie_samesite |
SameSite value for the browser-token cookie.
One of "Strict", "Lax", or "None". Defaults to "Strict" for maximum
protection against cross-site request forgery. Use "Lax" only when your
deployment requires the cookie to accompany top-level cross-site
navigations (for example, because of reverse-proxy flows), and document the
associated risk. If set to "None", the cookie will be marked
|
Details
Most apps only need to decide whether login starts automatically, whether to enable async mode, and whether token refresh should happen proactively. The remaining arguments are mainly for deployments that need tighter control over session lifetime, logout behavior, or browser cookie settings.
Blocking vs. async behavior: when
async = FALSE(the default), network operations like token exchange and refresh are performed on the main R thread. Transient errors are retried by the package's internalreq_with_retry()helper, which currently usesSys.sleep()for backoff. In Shiny,Sys.sleep()blocks the event loop for the entire worker process, potentially freezing UI updates for all sessions on that worker during slow provider responses or retry backoff. To keep the UI responsive: setasync = TRUEand configure an async backend that runs off the main process, such as mirai daemons (mirai::daemons(n)) or a non-sequential future plan, or reduce/block retries (seevignette("usage", package = "shinyOAuth")).Browser requirements: the module relies on the browser's Web Crypto API to generate a secure, per-session browser token used for state double-submit protection. Specifically, the login flow requires
window.crypto.getRandomValuesto be available. If it is not present (for example, in some very old or highly locked-down browsers), the module will be unable to proceed with authentication. In that case a client-side error is emitted and surfaced to the server asshinyOAuth_cookie_errorcontaining the message"webcrypto_unavailable". Use a modern browser (or enable Web Crypto) to resolve this.Browser cookie lifetime: the opaque browser token cookie lifetime mirrors the client's
state_storeTTL. Internally, the module readsclient@state_store$info()$max_ageand uses that value for the cookie'sMax-Age/Expires. When the cache does not expose a finitemax_age, a conservative default of 5 minutes (300 seconds) is used to align with the built-incachem::cache_mem(max_age = 300)default. Separately, the state payloadissued_atfreshness window is controlled by the client'sstate_payload_max_age(default 300 seconds).
Value
A reactiveValues object with token, error, error_description,
error_uri, and authenticated, plus additional fields used by the module.
The returned reactiveValues contains the following fields:
-
authenticated: logical TRUE when there is no error and a token is present and valid (matching the verifications enabled in the client provider); FALSE otherwise. Exception: whenindefinite_session = TRUE, errors do not affect this flag soauthenticatedremains TRUE even if refresh or other operations fail. -
token: OAuthToken object, or NULL if not yet authenticated. This contains the access token, refresh token (if any), ID token (if any), userinfo (if fetched), and the decoded ID token claims viatoken@id_token_claims(a read-only named list exposing all JWT payload claims such assub,acr,amr,auth_time, etc.). See OAuthToken for details. Because OAuthToken is a S7 object, you access its fields with@, e.g.,token@userinfoortoken@id_token_claims$acr. -
error: error code string when the OAuth flow fails. Be careful about showing this directly to users, because it may contain sensitive information. -
error_description: human-readable error detail when available. Be extra careful about showing this directly to users, because it may contain even more sensitive information. -
error_uri: URI identifying a human-readable web page with information about the error (per RFC 6749 section 4.1.2.1). Treat this as untrusted navigation input; shinyOAuth only surfaces absolute HTTPS values here and returns NULL when the provider omits or sends an unsafe value. -
browser_token: internal opaque browser cookie value; used for state double-submit protection; NULL if not yet set -
pending_callback: internal deferred callback payload; stores either list(type = "code", code, state, iss) for authorization-code callbacks or list(type = "error", error, error_description, error_uri, state, iss) for provider error callbacks. Used to defer callback handling untilbrowser_tokenis available; NULL otherwise. -
pending_login: internal logical; TRUE when a login was requested but must wait forbrowser_tokento be set, FALSE otherwise. -
auto_redirected: internal logical; TRUE once the module has initiated an automatic redirect in this session to avoid duplicate redirects. -
reauth_triggered: internal logical; TRUE once a reauthentication attempt has been initiated (after expiry or failed refresh), to avoid loops. -
auth_started_at: internal numeric timestamp (as fromSys.time()) when authentication started; NA if not yet authenticated. Used to enforcereauth_after_secondsif set. -
token_stale: logical; TRUE when the token was kept despite a refresh failure becauseindefinite_session = TRUE, or when the access token is past its expiry butindefinite_session = TRUEprevents automatic clearing. This lets UIs warn users or disable actions that require a fresh token. It resets to FALSE on successful login, refresh, or logout. -
last_login_async_used: internal logical; TRUE if the last login attempt usedasync = TRUE, FALSE if it was synchronous. This is only used for testing and diagnostics. -
refresh_in_progress: internal logical; TRUE while a token refresh is currently in flight (async or sync). Used to prevent concurrent refresh attempts when proactive refresh logic wakes up multiple times.
It also contains the following helper functions, mainly useful when
auto_redirect = FALSE and you want to start login from your own UI
(for example, from a button):
-
request_login(): initiates login by redirecting to the authorization endpoint, with cookie-ensure semantics: ifbrowser_tokenis missing, the module sets the cookie and defers the redirect untilbrowser_tokenis present, then redirects. If the module is already authenticated, the request is ignored and no new OAuth state is created. This is the main entry point for login whenauto_redirect = FALSE. -
logout(): if a token is present, makes best-effort revocation requests for the refresh token and access token when the provider exposes a revocation endpoint. This may perform network I/O, can revoke refresh tokens, and follows the module'sasyncsetting. It then clears the current token, setsauthenticatedto FALSE, and rotates the browser token cookie. You might call this when the user clicks a logout button. -
build_auth_url(): internal; builds and returns the authorization URL, also storing the relevant state in the client'sstate_store(for validation during callback). Note that this requiresbrowser_tokento be present, so it will throw an error if called too early. When the module is already authenticated it returnsNAand does not mint new state (verify withhas_browser_token()first). When PAR is used, the returned string keepsshinyOAuth.par_request_uri,shinyOAuth.par_expires_in, andshinyOAuth.par_expires_atattributes so manual link-style flows can decide when to regenerate it. Typically you would not call this directly, but userequest_login()instead, which calls it internally. -
set_browser_token(): internal; injects JS to set the browser token cookie if missing. Normally called automatically on first load, but you can call it manually if needed. If a token is already present, it will return immediately without changing it (callclear_browser_token()if you want to force a reset). Typically you would not call this directly, but userequest_login()instead, which calls it internally if needed. -
clear_browser_token(): internal; injects JS to clear the browser token cookie and clearsbrowser_token. You might call this to reset the cookie if you suspect it's stale or compromised. Typically you would not call this directly. -
has_browser_token(): internal; returns TRUE ifbrowser_tokenis present (non-NULL, non-empty), FALSE otherwise. Typically you would not call this directly
See Also
Examples
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"
)
# 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 <- fluidPage(
use_shinyOAuth(),
uiOutput("login")
)
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 <- fluidPage(
use_shinyOAuth(),
actionButton("login_btn", "Login"),
uiOutput("login")
)
server_2 <- function(input, output, session) {
auth <- oauth_module_server(
"auth",
client,
auto_redirect = FALSE
)
observeEvent(input$login_btn, {
auth$request_login()
})
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 <- fluidPage(
use_shinyOAuth(),
uiOutput("ui")
)
server_3 <- function(input, output, session) {
auth <- oauth_module_server(
"auth",
client,
auto_redirect = TRUE
)
repositories <- reactiveVal(NULL)
observe({
req(auth$authenticated)
# Example additional API request using the access token
# (e.g., fetch user repositories from GitHub)
resp <- perform_resource_req(
auth$token,
"https://api.github.com/user/repos"
)
if (httr2::resp_is_error(resp)) {
repositories(NULL)
} else {
repos_data <- httr2::resp_body_json(resp, simplifyVector = TRUE)
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 (!is.null(repos)) {
tags$ul(
Map(
function(url, name) {
tags$li(tags$a(href = url, target = "_blank", 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
)
}
}
Create generic OAuthProvider
Description
Helper to create an OAuthProvider object with sensible defaults. It is the main user-facing constructor for generic providers and is also used by the built-in provider helpers.
Usage
oauth_provider(
name,
auth_url,
token_url,
userinfo_url = NA_character_,
introspection_url = NA_character_,
revocation_url = NA_character_,
par_url = NA_character_,
require_pushed_authorization_requests = 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,
require_signed_request_object = FALSE,
request_parameter_supported = NA,
request_uri_parameter_supported = NA,
require_request_uri_registration = 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(),
tls_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,
allowed_algs = c("RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"),
allowed_token_types = c("Bearer"),
leeway = getOption("shinyOAuth.leeway", 30),
id_token_at_hash_required = FALSE
)
Arguments
name |
Provider name (e.g., "github", "google"). Cosmetic only; used in logging and audit events |
auth_url |
Authorization endpoint URL |
token_url |
Token endpoint URL |
userinfo_url |
User info endpoint URL (optional) |
introspection_url |
Token introspection endpoint URL (optional; RFC 7662) |
revocation_url |
Token revocation endpoint URL (optional; 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 |
require_pushed_authorization_requests |
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. |
require_signed_request_object |
Logical. Whether the provider requires
signed Request Objects for authorization requests. When |
request_parameter_supported |
Logical or |
request_uri_parameter_supported |
Logical or |
require_request_uri_registration |
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 |
tls_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 OIDC issuer URL. You need this when you want ID token
validation. shinyOAuth uses it to verify the ID token |
issuer_match |
Character scalar controlling how strictly the discovery
document's
In most cases, keep the default |
use_nonce |
Whether to use OIDC nonce. This adds a |
use_pkce |
Whether to use PKCE. This adds 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 userinfo after token exchange.
User information will be stored in the For the low-level constructor |
userinfo_id_token_match |
Whether to fail closed if UserInfo cannot be
bound to a validated ID token subject. Whenever both UserInfo and a
validated ID token are available, shinyOAuth compares the validated ID token
For |
userinfo_signed_jwt_required |
Whether to require that the userinfo
endpoint returns a signed JWT (
This prevents unsigned or weakly signed userinfo payloads from being treated
as trusted identity data. Requires Note: |
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 user identifier, such
as audit fields and UserInfo-to-ID-token subject matching. If you configure a
selector other than |
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 Note: At the S7 class level, this defaults to FALSE so that pure OAuth 2.0
providers can be configured without OIDC. Helper constructors like
|
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
Note: At the S7 class level, this defaults to FALSE. Helper constructors like
|
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). shinyOAuth applies these headers to token exchange, refresh, introspection, revocation, and PAR requests. Use this only for headers you intentionally want on that full set of authorization-server calls. |
token_auth_style |
How the client authenticates at the token endpoint. One of:
|
jwks_cache |
Cache used for the provider's signing keys (JWKS). If not
provided, shinyOAuth creates an in-memory cache for 1 hour with
In most cases, a TTL between 15 minutes and 2 hours is reasonable. Shorter
TTLs pick up new keys faster but do more network work; longer TTLs reduce
traffic but may take longer to notice key rotation. If a new |
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., |
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 always include |
leeway |
Clock skew leeway (seconds) applied to ID token |
id_token_at_hash_required |
Whether to require the |
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
# See `?oauth_provider_microsoft` for example using a custom tenant ID
# 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()) {
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 Auth0 OAuthProvider (via OIDC discovery)
Description
Create an Auth0 OAuthProvider (via OIDC discovery)
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
# 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
# See `?oauth_provider_microsoft` for example using a custom tenant ID
# 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()) {
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 a GitHub OAuthProvider
Description
Ready-to-use OAuth 2.0 provider settings for GitHub.
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 'Developer Settings'.
Value
OAuthProvider object for use with a GitHub OAuth 2.0 app
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
# See `?oauth_provider_microsoft` for example using a custom tenant ID
# 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()) {
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 a Google OAuthProvider
Description
Ready-to-use OAuthProvider settings for Google.
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.
Value
OAuthProvider object for use with a Google OAuth 2.0 app
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
# See `?oauth_provider_microsoft` for example using a custom tenant ID
# 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()) {
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 a Keycloak OAuthProvider (via OIDC discovery)
Description
Create a Keycloak OAuthProvider (via OIDC discovery)
Usage
oauth_provider_keycloak(
base_url,
realm,
name = paste0("keycloak-", realm),
token_auth_style = "body"
)
Arguments
base_url |
Base URL of the Keycloak server, e.g., "http://localhost:8080" |
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 |
Value
OAuthProvider object configured for the specified Keycloak realm
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
# See `?oauth_provider_microsoft` for example using a custom tenant ID
# 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()) {
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 a Microsoft (Entra ID) OAuthProvider
Description
Ready-to-use OAuthProvider settings for Microsoft Entra ID (formerly Azure AD) using the v2.0 endpoints. Accepts a tenant identifier and configures the authorization, token, and userinfo endpoints directly.
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
Most users only need to choose the tenant and decide whether to keep ID token validation enabled. The remaining details below explain how the helper behaves for Microsoft's different tenant styles.
The tenant can be one of the special values "common", "organizations",
or "consumers", or a specific directory (tenant) ID GUID
(e.g., "00000000-0000-0000-0000-000000000000").
When tenant is a specific GUID, the provider enables strict ID token
validation with the tenant-specific issuer.
For tenant = "common" or tenant = "organizations", the helper enables
Microsoft Entra's tenant-independent validation mode by default: ID tokens
are checked against Microsoft's {tenantid} issuer template and the signing
key's own issuer scope, as documented by Microsoft for multi-tenant
metadata. Runtime JWKS discovery for these aliases also uses host-only
discovery issuer matching because Microsoft's tenant-independent metadata
publishes a templated issuer rather than echoing the alias URL exactly.
For tenant = "consumers", the helper resolves the stable consumer tenant
issuer (9188040d-6c67-4c5b-b112-36a304b66dad) and performs normal exact-
issuer validation.
Set id_token_validation = FALSE to opt out of ID token and nonce
validation for these aliases, which falls back to OAuth 2.0 plus userinfo
identity only.
Microsoft issues RS256 ID tokens; allowed_algs is restricted accordingly.
The userinfo endpoint is provided by Microsoft Graph
(https://graph.microsoft.com/oidc/userinfo).
When configuring your OAuthClient, if you do not have the option to
register an app or simply wish to test during development, you may be able
to use the default Azure CLI public app, with client_id
'04b07795-8ddb-461a-bbee-02f9e1bf7b46' (uses redirect_uri
'http://localhost:8100').
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")
) {
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")
),
# Default Azure CLI app ID (public client; activated in many tenants):
client_id = "04b07795-8ddb-461a-bbee-02f9e1bf7b46",
redirect_uri = "http://localhost:8100",
scopes = c("openid", "profile", "email")
)
# UI
ui <- fluidPage(
use_shinyOAuth(),
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")
)
# 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$token)
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
Helper for providers that follow a standard OpenID Connect endpoint layout.
It builds the usual OIDC endpoints from one base URL and then calls
oauth_provider() with OIDC-friendly defaults.
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"),
...
)
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 |
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
# See `?oauth_provider_microsoft` for example using a custom tenant ID
# 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()) {
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
Builds an OAuthProvider from the provider's OpenID Connect discovery
document at /.well-known/openid-configuration. When present,
introspection_endpoint is also wired into the resulting provider.
Usage
oauth_provider_oidc_discover(
issuer,
name = NULL,
use_pkce = TRUE,
use_nonce = TRUE,
id_token_validation = TRUE,
token_auth_style = NULL,
allowed_algs = c("RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"),
allowed_token_types = c("Bearer"),
jwks_host_issuer_match = TRUE,
issuer_match = c("url", "host", "none"),
...
)
Arguments
issuer |
The OIDC issuer base URL (including scheme), e.g., "https://login.example.com" |
name |
Optional friendly provider name. Defaults to the issuer hostname |
use_pkce |
Logical, whether to use PKCE for this provider. Defaults to
TRUE. If the discovery document indicates |
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 |
allowed_algs |
Character vector of allowed ID token signing algorithms. Defaults to a broad set of common algorithms, including RSA (RS*), ECDSA (ES*), and 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 |
Details
Most users can accept the defaults here. The points below are mainly reference for advanced provider setups or for understanding why discovery might fail early.
ID token algorithms: by default this helper accepts common asymmetric algorithms RSA (RS*), ECDSA (ES*), and EdDSA. When the provider advertises its supported ID token signing algorithms via
id_token_signing_alg_values_supported, the helper uses the intersection with the caller-providedallowed_algs. If there is no overlap, discovery fails with a configuration error. There is no automatic fallback to the discovery-advertised set.Token endpoint authentication methods: supports
client_secret_basic(header),client_secret_post(body), public clients usingnone(mapped totoken_auth_style = "public"when PKCE is enabled), as well as JWT-based methodsprivate_key_jwtandclient_secret_jwtper RFC 7523. Discovery also preserves RFC 8705 mTLS metadata (mtls_endpoint_aliasesandtls_client_certificate_bound_access_tokens) and supports explicittls_client_auth/self_signed_tls_client_authselection.PAR metadata: when the discovery document advertises
pushed_authorization_request_endpointorrequire_pushed_authorization_requests, the resulting provider stores that PAR capability and policy metadata so authorization requests can use RFC 9126 PAR and fail fast on PAR-only provider policies.Request Object metadata: when the discovery document advertises
request_object_signing_alg_values_supportedorrequire_signed_request_object, the resulting provider stores that metadata soOAuthClientcan fail fast when a request-object algorithm is unsupported or when the provider requires signed Request Objects. When the discovery document also advertisesrequest_object_encryption_alg_values_supportedorrequest_object_encryption_enc_values_supported, the resulting provider stores that encryption metadata so Request Object JWE configuration can be validated early as well.Authorization request transport metadata: when the discovery document advertises
request_parameter_supported,request_uri_parameter_supported, orrequire_request_uri_registration, the resulting provider stores that metadata so shinyOAuth can fail fast when a provider explicitly disallows the front-channelrequesttransport used by JAR or caller-managedrequest_urivalues. The registration requirement itself remains deployment-specific: shinyOAuth storesrequire_request_uri_registrationfor caller awareness, but it cannot independently verify whether the provider has already registered a matching publicrequest_urior wildcard prefix for the client. When PAR is configured, shinyOAuth sends signed Request Objects to the PAR endpoint and the browser redirect only carries the PAR-issuedrequest_urihandle, regardless ofrequest_uri_parameter_supportedorrequire_request_uri_registration. When discovery omits these booleans, this helper applies the OpenID Connect defaults instead of storingNA.Response mode metadata: when the discovery document advertises
response_modes_supported, the resulting provider stores it so explicitresponse_moderequests can fail fast when unsupported. When the metadata is omitted, this helper applies the OAuth/OIDC metadata default ofc("query", "fragment").Token endpoint JWT auth metadata: when the discovery document advertises
token_endpoint_auth_signing_alg_values_supported, the resulting provider stores that metadata soOAuthClientcan fail fast when a JWT client assertion algorithm is unsupported.DPoP metadata: when the discovery document advertises
dpop_signing_alg_values_supported, the resulting provider stores that metadata soOAuthClientcan fail fast when an explicit or inferred DPoP proof signing algorithm is unsupported.RFC 9207 callback issuer metadata: when the discovery document advertises
authorization_response_iss_parameter_supported = true, the resulting provider stores that metadata sooauth_client()can auto-enable callback issuer enforcement unless you explicitly opt out.PKCE method discovery: this helper keeps
S256as the default and does not silently downgrade toplain. If discovery metadata explicitly omitsS256, discovery fails with a configuration error unless you explicitly opt intopkce_method = "plain".Important: discovery metadata lists methods supported across the provider, not per-client provisioning. This helper does not automatically select JWT-based methods just because they are advertised. By default it prefers
client_secret_basic(header) when available, otherwiseclient_secret_post(body), and maps publicnonetotoken_auth_style = "public"only for PKCE clients. If a provider advertises only JWT methods, you must explicitly settoken_auth_styleand configure the corresponding credentials on your OAuthClient (a private key forprivate_key_jwt, or a sufficiently strongclient_secretforclient_secret_jwt).Host policy: by default, discovered standard endpoints must be absolute URLs whose host matches the issuer host exactly. Subdomains are NOT implicitly allowed. If you want to allow subdomains, add a leading-dot or glob in
options(shinyOAuth.allowed_hosts), e.g.,.example.comor*.example.com. If a global whitelist is supplied viaoptions(shinyOAuth.allowed_hosts), discovery will restrict endpoints to that whitelist. RFC 8705mtls_endpoint_aliasesare validated separately: they may use a different host or port by default, but an explicitshinyOAuth.allowed_hostswhitelist still constrains them. Scheme policy (https/http for loopback) is delegated tois_ok_host(), so you may allow non-HTTPS hosts withoptions(shinyOAuth.allowed_non_https_hosts)(see?is_ok_host).
Value
OAuthProvider object configured from discovery
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
# See `?oauth_provider_microsoft` for example using a custom tenant ID
# 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()) {
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
Create an Okta OAuthProvider (via OIDC discovery)
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 (default "default") |
name |
Optional provider name (default "okta") |
Value
OAuthProvider object configured for the specified Okta domain
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
# See `?oauth_provider_microsoft` for example using a custom tenant ID
# 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()) {
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 a Slack OAuthProvider (via OIDC discovery)
Description
Create a Slack OAuthProvider (via OIDC discovery)
Usage
oauth_provider_slack(name = "slack")
Arguments
name |
Optional provider name (default "slack") |
Value
OAuthProvider object configured for Slack
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
# See `?oauth_provider_microsoft` for example using a custom tenant ID
# 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()) {
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 a Spotify OAuthProvider
Description
Ready-to-use OAuth 2.0 provider settings for Spotify.
It uses /v1/me as the user profile endpoint and does not expect ID tokens.
Usage
oauth_provider_spotify(name = "spotify")
Arguments
name |
Optional provider name (default "spotify") |
Details
Spotify requires scopes to be included in the authorization request.
Set requested scopes on the client with oauth_client(..., scopes = ...).
Value
OAuthProvider object for use with a Spotify OAuth 2.0 app
See Also
For an example application which using Spotify OAuth 2.0 login to
display the user's listening data, see vignette("example-spotify").
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
# See `?oauth_provider_microsoft` for example using a custom tenant ID
# 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()) {
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")
}
Alias for perform_resource_req()
Description
Deprecated alias for perform_resource_req() to avoid a breaking change in the public API.
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,
oauth_client = NULL,
token_type = NULL,
dpop_nonce = NULL,
idempotent = 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. If |
check_url |
Logical. If |
oauth_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 |
Optional logical controlling generic transport and
transient-HTTP retries in |
Value
Same value as perform_resource_req().
Build and perform an authenticated httr2 request for a protected resource
Description
This is a helper for calling downstream APIs with an access token. It creates
an httr2::request() for the given URL, attaches the right authorization
header for the token type, applies shinyOAuth's standard HTTP defaults, and
performs the request. You can also provide a prebuilt httr2::request() object
as the url argument, in which case this helper will layer token authentication
and any explicit overrides on top of the provided request before performing it.
Use resource_req() if you want to only build the request (and perform it later).
Compared to httr2::req_perform(), this helper adds shinyOAuth-specific
handling for DPoP-bound tokens, including retrying once with a fresh proof when
a DPoP-Nonce challenge is encountered. For non-DPoP tokens, this helper behaves
similarly to httr2::req_perform() but with the package's standard defaults
for retries and redirects.
Usage
perform_resource_req(
token,
url,
method = "GET",
headers = NULL,
query = NULL,
follow_redirect = FALSE,
check_url = TRUE,
oauth_client = NULL,
token_type = NULL,
dpop_nonce = NULL,
idempotent = 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. If |
check_url |
Logical. If |
oauth_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 |
Optional logical controlling generic transport and
transient-HTTP retries in |
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()) {
# Get an OAuthToken
# (typically provided as reactive return value by `oauth_module_server()`)
token <- OAuthToken()
# 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)
)
httr2::req_dry_run(request)
# 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)
}
Prepare a OAuth 2.0 authorization call and build an authorization URL
Description
Prepares an OAuth 2.0 authorization request and returns the browser redirect URL. It generates the needed state, PKCE, and nonce values, stores the one-time callback data, and builds the final authorization URL.
Usage
prepare_call(oauth_client, browser_token, request_uri_publisher = NULL)
Arguments
oauth_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
|
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
# Please note: `prepare_call()` & `handle_callback()` are typically
# not called by users of this package directly, but are called
# internally by `oauth_module_server()`. These functions are exported
# nonetheless for advanced use cases. Most users will not need to
# call these functions directly
# Below code shows generic usage of `prepare_call()` and `handle_callback()`
# (code is not run because it would require user interaction)
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 authorization URL and and store state in client's state store
# `<browser_token>` is a token that identifies the browser session
# and would typically be stored in a browser cookie
# (`oauth_module_server()` handles this typically)
authorization_url <- prepare_call(client, "<browser_token>")
# Redirect user to authorization URL; retrieve code & payload from query;
# read also `<browser_token>` from browser cookie
# (`oauth_module_server()` handles this typically)
code <- "..."
payload <- "..."
browser_token <- "..."
# Handle callback, exchanging code for token and validating state
# (`oauth_module_server()` handles this typically)
token <- handle_callback(client, code, payload, browser_token)
}
Refresh an OAuth 2.0 token
Description
Refreshes an OAuth session by obtaining a new access token with the refresh token. When configured, shinyOAuth also re-fetches userinfo and validates any new ID token returned by the provider.
Per OIDC Core Section 12.2, providers may omit the ID token from refresh responses. When omitted, the original ID token from the initial login is preserved.
If the provider does return a new ID token during refresh, refresh_token()
requires that an original ID token from the initial login is available so it
can enforce subject continuity (OIDC 12.2: sub MUST match). If no original
ID token is available, refresh fails with an error.
When id_token_validation = TRUE, any refresh-returned ID token is also
fully validated (signature and claims) in addition to the OIDC 12.2 sub
continuity check.
When userinfo_required = TRUE, userinfo is re-fetched using the fresh
access token. Whenever shinyOAuth has both refreshed userinfo and a
validated ID token baseline, it checks that their sub claims still match.
If userinfo_id_token_match = TRUE, the absence of a trustworthy ID token
baseline is treated as an error instead of silently accepting unbound
userinfo data.
Usage
refresh_token(
oauth_client,
token,
async = FALSE,
introspect = FALSE,
shiny_session = NULL
)
Arguments
oauth_client |
OAuthClient object |
token |
OAuthToken object containing the refresh token |
async |
Logical, default FALSE. If TRUE and an async backend is
configured, the refresh is dispatched through shinyOAuth's async promise
path and this function returns a promise-compatible async result that
resolves to an updated |
introspect |
Logical, default FALSE. After a successful refresh, if the
provider exposes an introspection endpoint, introspect the new access
token for validation and audit/diagnostics. When enabled, refresh fails
if introspection is unsupported, inactive, or missing required
|
shiny_session |
Optional pre-captured Shiny session context (from
|
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 finite fallback expiry fromresolve_missing_expires_in() -
refresh_token: Updated if the provider rotates it; otherwise preserved -
id_token: Updated only if the provider returns one (and it validates); otherwise the original from login is preserved -
userinfo: Refreshed ifuserinfo_required = TRUE; otherwise preserved -
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.
Examples
# Please note: `get_userinfo()`, `introspect_token()`, and `refresh_token()`
# are typically not called by users of this package directly, but are called
# internally by `oauth_module_server()`. These functions are exported
# nonetheless for advanced use cases. Most users will not need to
# call these functions directly
# Example requires a real token from a completed OAuth flow
# (code is therefore not run; would error with placeholder values below)
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"
)
# Have a valid OAuthToken object; fake example below
# (typically provided by `oauth_module_server()` or `handle_callback()`)
token <- handle_callback(client, "<code>", "<payload>", "<browser_token>")
# Get userinfo
user_info <- get_userinfo(client, token)
# Introspect token (if supported by provider)
introspection <- introspect_token(client, token)
# Refresh token
new_token <- refresh_token(client, token, introspect = TRUE)
}
Build an authenticated httr2 request for a protected resource
Description
This is a helper for calling downstream APIs with an access token. It creates an
httr2::request() for the given URL, attaches the right authorization header
for the token type, and applies shinyOAuth's standard HTTP defaults.
Use perform_resource_req() when you want shinyOAuth to also perform the request
and handle DPoP nonce challenges for you (which httr2::req_perform()
would not do on its own).
Usage
resource_req(
token,
url,
method = "GET",
headers = NULL,
query = NULL,
follow_redirect = FALSE,
check_url = TRUE,
oauth_client = NULL,
token_type = NULL,
dpop_nonce = 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. If |
check_url |
Logical. If |
oauth_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
|
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). Adding query parameters after resource_req() is fine, but
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()) {
# Get an OAuthToken
# (typically provided as reactive return value by `oauth_module_server()`)
token <- OAuthToken()
# 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)
)
httr2::req_dry_run(request)
# 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)
}
Revoke an OAuth 2.0 token
Description
Attempts to revoke an access or refresh token when the provider exposes a revocation endpoint (RFC 7009).
Authentication mirrors the provider's token_auth_style (same as token
exchange and introspection).
Best-effort semantics:
If the provider does not expose a revocation endpoint, returns
supported = FALSE,revoked = NA, andstatus = "revocation_unsupported".If the selected token value is missing, returns
supported = TRUE,revoked = NA, andstatus = "missing_token".If the endpoint returns a 2xx, returns
supported = TRUE,revoked = TRUE, andstatus = "ok".If the endpoint returns an HTTP error, returns
supported = TRUE,revoked = NA, andstatus = "http_<code>".
Usage
revoke_token(
oauth_client,
oauth_token,
which = c("refresh", "access"),
async = FALSE,
shiny_session = NULL
)
Arguments
oauth_client |
OAuthClient object |
oauth_token |
OAuthToken object containing tokens to revoke |
which |
Which token to revoke: "refresh" (default) or "access" |
async |
Logical, default FALSE. If TRUE and an async backend is
configured, the operation is dispatched through shinyOAuth's async
promise path and this function returns a promise-compatible async result
that resolves to the result list. mirai is preferred when daemons are
configured via |
shiny_session |
Optional pre-captured Shiny session context (from
|
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>".
Decrypt and validate OAuth state payload
Description
Internal utility that decrypts the encrypted state payload using the
client's state_key, then validates freshness and client binding. Used by
callback handling before the code exchange continues.
Usage
state_payload_decrypt_validate(
client,
encrypted_payload,
shiny_session = NULL,
audit_success = FALSE
)
Arguments
client |
OAuthClient instance |
encrypted_payload |
Encrypted state payload string received via the
|
shiny_session |
Optional pre-captured Shiny session context (from
|
audit_success |
Whether successful payload validation should emit the
standard callback validation audit event. Defaults to |
Value
A named list payload (state, client_id, redirect_uri, scopes,
provider, client_policy, issued_at) on success; otherwise throws an error
via err_invalid_state().
Fetch and remove the single-use state entry
Description
Uses the client's state_store to read and remove the state-bound values
after the encrypted callback payload has been decrypted and validated.
Usage
state_store_get_remove(client, state, shiny_session = NULL)
Arguments
client |
OAuthClient instance |
state |
Plain (decrypted) state string used as the logical key |
shiny_session |
Optional pre-captured Shiny session context (from
|
Details
When the store exposes an atomic $take(key, missing) method (see
custom_cache()), that path is used first so single-use semantics still
hold under concurrent access.
When $take() is unavailable, the function falls back to $get() +
$remove() with a post-removal absence check.
That fallback is safe for per-process caches such as cachem::cache_mem().
For shared stores it errors by default, because non-atomic get+remove cannot
guarantee single-use semantics under concurrent access; operators may opt in
to that weaker fallback with
options(shinyOAuth.allow_non_atomic_state_store = TRUE), but doing so is
discouraged.
Value
Validated state-store value list. On failure this function raises
err_invalid_state() instead of returning a partial result.
Add JavaScript dependency to the UI of a Shiny app
Description
Adds shinyOAuth's client-side JavaScript dependency to your Shiny UI. This is required so the module can handle redirects and manage its browser-side session token.
Without this call in the UI, oauth_module_server() will not work unless
your app UI is wrapped with oauth_form_post_ui(), which injects this
dependency automatically for form_post flows.
Usage
use_shinyOAuth(inject_referrer_meta = TRUE)
Arguments
inject_referrer_meta |
If TRUE (default), injects a
|
Details
Place this near the top-level of your UI (e.g., inside fluidPage() or
tagList()), similar to how you would use shinyjs::useShinyjs(). If you
wrap the app UI with oauth_form_post_ui(), you usually do not need a
separate call here because that wrapper injects this dependency for you.
Value
A tagList that loads the inst/www/shinyOAuth.js dependency once.
See Also
Examples
ui <- shiny::fluidPage(
use_shinyOAuth(),
# ...
)