S7 class representing an OAuth 2.0 client configuration, including a provider, client credentials, redirect URI, requested scopes, and state management.
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_,
redirect_uri = character(0),
scopes = 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 = "strict",
claims_validation = "none",
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:
Required (non-empty) when the provider authenticates the client with HTTP Basic auth at the token endpoint (
token_auth_style = "header", also known asclient_secret_basic).Optional for public PKCE-only clients when the provider is configured with
use_pkce = TRUEand uses form-body client authentication at the token endpoint (token_auth_style = "body", also known asclient_secret_post). In this case, the secret is omitted from token requests.
Note: If your provider issues HS256 ID tokens and
id_token_validationis enabled, a non-emptyclient_secretis required for signature validation.- client_private_key
Optional private key for
private_key_jwtclient authentication at the token endpoint. Can be anopenssl::keyor a PEM string containing a private key. Required when the provider'stoken_auth_style = 'private_key_jwt'. Ignored for other auth styles.- client_private_key_kid
Optional key identifier (kid) to include in the JWT header for
private_key_jwtassertions. Useful when the authorization server uses kid to select the correct verification key.- client_assertion_alg
Optional JWT signing algorithm to use for client assertions. When omitted, defaults to
HS256forclient_secret_jwt. Forprivate_key_jwt, a compatible default is selected based on the private key type/curve (e.g.,RS256for RSA,ES256/ES384/ES512for EC P-256/384/521, orEdDSAfor Ed25519/Ed448). If an explicit value is provided but incompatible with the key, validation fails early with a configuration error. Supported values areHS256,HS384,HS512for client_secret_jwt and asymmetric algorithms supported byjose::jwt_encode_sig(e.g.,RS256,PS256,ES256,EdDSA) for private keys.- client_assertion_audience
Optional override for the
audclaim used when building JWT client assertions (client_secret_jwt/private_key_jwt). By default, shinyOAuth uses the exact token endpoint request URL. Some identity providers require a different audience value; set this to the exact value your IdP expects.- redirect_uri
Redirect URI registered with provider
- scopes
Vector of scopes to request
- 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:
NULL(default): no claims parameter is sentA list: automatically JSON-encoded (via
jsonlite::toJSON()withauto_unbox = TRUE) and URL-encoded into the authorization request. The list should have top-level membersuserinfoand/orid_token, each containing named lists of claims. UseNULLto request a claim without parameters (per spec). Example:list(userinfo = list(email = NULL, given_name = list(essential = TRUE)), id_token = list(auth_time = list(essential = TRUE)))Note on single-element arrays: because
auto_unbox = TRUEis used, single-element R vectors are serialized as JSON scalars, not arrays. The OIDC spec definesvaluesas an array. To force array encoding for a single-element vector, wrap it inI(), e.g.,acr = list(values = I("urn:mace:incommon:iap:silver"))produces{"values":["urn:mace:incommon:iap:silver"]}. Multi-element vectors are always encoded as arrays.A character string: pre-encoded JSON string (for advanced use). Must be valid JSON. Use this when you need full control over JSON encoding. Note: The
claimsparameter is OPTIONAL per OIDC Core §5.5. Not all providers support it; consult your provider's documentation.
- state_store
State storage backend. Defaults to
cachem::cache_mem(max_age = 300). Alternative backends should usecustom_cache()with an atomic$take()method for replay-safe single-use state consumption. The backend must implement cachem-like methods$get(key, missing),$set(key, value), and$remove(key);$info()is optional.Trade-offs:
cache_memis in-memory and thus scoped to a single R process (good default for a single Shiny process). For multi-process deployments, usecustom_cache()with an atomic$take()backed by a shared store (e.g., RedisGETDEL, SQLDELETE ... RETURNING). Plaincachem::cache_disk()is not safe as a shared state store because its$get()+$remove()operations are not atomic; use it only if wrapped in acustom_cache()that provides$take(). See alsovignette("usage", package = "shinyOAuth").The client automatically generates, persists (in
state_store), and validates the OAuthstateparameter (and OIDCnoncewhen applicable) during the authorization code flow- state_payload_max_age
Positive number of seconds. Maximum allowed age for the decrypted state payload's
issued_attimestamp during callback validation.This value is an independent freshness backstop against replay attacks on the encrypted
statepayload. It is intentionally decoupled fromstate_storeTTL (which controls how long the single-use state entry can exist in the server-side cache, and also drives browser cookie max-age inoauth_module_server()).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
validate_state()'s default minimum which targets ~128 bits for base64url‑like strings). Default is 64, which provides approximately 384 bits of entropy- 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
statequery parameter. This provides confidentiality and integrity (via authentication tag) for the embedded data used during callback verification. If you omit this argument, a random value is generated viarandom_urlsafe(128). This key is distinct from the OAuthclient_secretand may be used with public clients.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, you must configure a shared
state_storeand the samestate_keyacross all workers. Otherwise callbacks that land on a different worker will be unable to decrypt/validate the state envelope and authentication will fail. In such environments, do not rely on the random per-process default: provide an explicit, high-entropy key (for example via a secret store or environment variable). Prefer values with substantial entropy (e.g., 64–128 base64url characters or a raw 32+ byte key). Avoid human‑memorable passphrases. See alsovignette("usage", package = "shinyOAuth").- 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.
"strict"(default): Throws an error if any requested scope is missing from the granted scopes."warn": Emits a warning but continues authentication if scopes are missing."none": Skips scope validation entirely.
- claims_validation
Controls validation of essential claims requested via the
claimsparameter (OIDC Core §5.5). Whenclaimsincludes entries withessential = TRUEforid_tokenoruserinfo, this setting determines what happens if those essential claims are missing from the returned ID token or userinfo response."none"(default): Skips claims validation entirely. This is the default because providers are expected to fulfil essential claims requests or return an error."warn": Emits a warning but continues authentication if essential claims are missing."strict": Throws an error if any requested essential claims are missing from the response.
- 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
acrclaim whose value is one of the specified entries; otherwise the login fails with ashinyOAuth_id_token_error.Additionally, when non-empty, the authorization request automatically includes an
acr_valuesquery parameter (space-separated) as a voluntary hint to the provider (OIDC Core §3.1.2.1). Note that the provider is not required to honour this hint; the client-side validation is the authoritative enforcement.Requires an OIDC-capable provider with
id_token_validation = TRUEand anissuerconfigured. Default ischaracter(0)(no enforcement).- 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
active = TRUE; otherwise the login fails andauthenticatedremains FALSE. Default is FALSE. Requires the provider to have anintrospection_urlconfigured.- introspect_elements
Optional character vector of additional requirements to enforce on the introspection response when
introspect = TRUE. Supported values:"sub": require the introspectedsubto match the session subject (from ID tokensubwhen available, else from userinfosub)."client_id": require the introspectedclient_idto match your OAuth client id."scope": validate introspectedscopeagainst requested scopes (respects the client'sscope_validationmode). Default ischaracter(0). (Note that not all providers may return each of these fields in introspection responses.)
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)
req <- client_bearer_req(auth$token, "https://api.github.com/user/repos")
resp <- httr2::req_perform(req)
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
)
}
}