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",
authorization_request_signing_alg = NA_character_,
authorization_request_audience = NA_character_,
authorization_request_ttl = 120,
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 = "strict",
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:
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 when the provider uses form-body client authentication at the token endpoint (
token_auth_style = "body", also known asclient_secret_post) anduse_pkce = TRUE. In that configuration, the secret is omitted only when it is empty.Ignored for token-endpoint authentication when the provider uses
token_auth_style = "public"(or the alias"none"). Public auth sendsclient_idonly and never sendsclient_secret, even if one is configured or picked up fromOAUTH_CLIENT_SECRET.
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. Current outbound private-key JWT signing supports RSA and EC private keys. For RSA keys, outbound signing is currently limited toRS256;RS384,RS512, and RSA-PSS (PS256,PS384,PS512) are not supported. Ed25519/Ed448 keys are also not currently supported.- 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 orES256/ES384/ES512for EC P-256/384/521). If an explicit value is provided but incompatible with the key, validation fails early with a configuration error. When the provider advertisestoken_endpoint_auth_signing_alg_values_supported, both explicit values and inferred defaults must be included in that set. Supported values areHS256,HS384,HS512for client_secret_jwt and asymmetric algorithms supported for outbound signing (RS256,ES256,ES384,ES512) for private keys.RS384,RS512,PS256,PS384,PS512, andEdDSAare not currently supported for outbound client assertions.- 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.- 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
provider@token_auth_styleis"tls_client_auth"or"self_signed_tls_client_auth".- tls_client_key_file
Optional path to the PEM-encoded private key used with
tls_client_cert_file. Must be supplied together withtls_client_cert_file, and is required for RFC 8705 mTLS client authentication.- tls_client_key_password
Optional password used to decrypt an encrypted PEM private key referenced by
tls_client_key_file.- 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
FALSE.Set this to
TRUEfor clients that should prefer discoveredmtls_endpoint_aliaseson authorization-server requests even whentoken_auth_styleitself is not an mTLS auth style, and that should fail closed if the returned access token omitscnf.x5t#S256.Requires
tls_client_cert_fileandtls_client_key_file, and the provider must be configured withtls_client_certificate_bound_access_tokens = TRUE.Controls how the authorization request is transported to the provider.
"parameters"(default): send OAuth parameters directly on the browser redirect URL."request": send a signed JWT-secured authorization request (JAR; RFC 9101) via therequestparameter.
Most users can keep the default. Request mode is an advanced option that requires signing material on the client. shinyOAuth prefers
client_private_keywhen present; otherwise it falls back to HMAC signing withclient_secret.Optional JWS algorithm override for signed authorization requests when
authorization_request_mode = "request". When omitted, shinyOAuth choosesHS256for HMAC-based signing or a compatible asymmetric default based onclient_private_key(for exampleRS256,ES256,ES384, orES512).RS384,RS512,PS256,PS384,PS512, andEdDSAare not currently supported for outbound signed authorization requests.Optional override for the
audclaim used in signed authorization requests. By default, shinyOAuth uses the provider issuer when available. If the provider has no configured issuer, shinyOAuth omits theaudclaim unless you supply an explicit override.Positive number of seconds to keep signed authorization request objects (
requestJWTs) valid. Default is120.Optional non-negative number of seconds. When provided, shinyOAuth adds an
nbfclaim set toiat - authorization_request_nbf_skewso deployments can tolerate small clock skew while still emitting bounded request-object validity windows. LeaveNULL(the default) to omitnbfunless you supply one explicitly through extra authorization parameters.- dpop_private_key
Optional private key used to generate DPoP proofs (RFC 9449). Can be an
openssl::keyor a PEM string containing an asymmetric private key. When provided, shinyOAuth can attachDPoPproofs to token endpoint requests and use DPoP-bound access tokens in downstream request helpers. Inoauth_client(), configuring this key also makesdpop_require_access_tokendefault toTRUE, so access-token responses rejecttoken_type = "Bearer"unless you explicitly setdpop_require_access_token = FALSE. Current outbound DPoP signing supports RSA and EC private keys. For RSA keys, outbound signing is currently limited toRS256;RS384,RS512, and RSA-PSS (PS256,PS384,PS512) are not supported. Ed25519/Ed448 keys are also not currently supported. This is an advanced setting; most clients do not need DPoP unless their provider or resource server asks for it.- dpop_private_key_kid
Optional key identifier (
kid) to include in the JOSE header of DPoP proofs. Useful when the authorization or resource server expects a stable key identifier alongside the embedded public JWK.- 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
RS256,ES256,ES384, orES512).RS384,RS512,PS256,PS384,PS512, andEdDSAare not currently supported for outbound DPoP proofs. If an explicit value is provided but incompatible with the key, validation fails early with a configuration error.- dpop_require_access_token
Logical or
NULL. WhenTRUEanddpop_private_keyis configured, shinyOAuth requires the authorization server to returntoken_type = "DPoP"for access tokens and fails fast otherwise. Inoauth_client(), the defaultNULLresolves toTRUEwhendpop_private_keyis configured and toFALSEotherwise. SetFALSEexplicitly only when you intentionally want to allow Bearer access tokens, such as deployments where DPoP is used only to bind refresh tokens.- redirect_uri
Redirect URI registered with provider
- enforce_callback_issuer
Logical or
NULL. WhenTRUE, enforce that authorization responses handled through this client include an RFC 9207issparameter and reject callbacks unless it exactly matchesprovider@issuer. This is recommended when one callback URL can receive responses from more than one authorization server. Requires the provider to have a configuredissuer.When
NULL(theoauth_client()helper default), shinyOAuth auto-enables this check for providers that advertiseauthorization_response_iss_parameter_supported = TRUEand have a configuredissuer, such as OIDC discovery providers that expose RFC 9207 support. SetFALSEto opt out explicitly.- scopes
Vector of scopes to request. For OIDC providers (those with an
issuer), shinyOAuth automatically prependsopenidwhen it is missing; that effective scope set is what gets sent in the authorization request and used for later state and token-scope validation.- 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
resourceparameter on the authorization request, initial token exchange, and token refresh requests. Default ischaracter(0).- 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 (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.cachem::cache_mem()is a good default for a single Shiny process. For multi-process deployments, usecustom_cache()with an atomic$take()backed by a shared store (for example RedisGETDELor SQLDELETE ... RETURNING). Plaincachem::cache_disk()is not safe as a shared state store because its$get()+$remove()operations are not atomic.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 is the freshness window for the sealed
statepayload itself. It is separate from thestate_storeTTL, which controls how long the one-time server-side state entry can exist.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.- 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, configure a shared
state_storeand the samestate_keyacross all workers. Otherwise callbacks that land on a different worker will fail state validation.- 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
scopewhen it is unchanged from the requested scope."strict"(default): Throws an error if any requested scope is missing from the granted scopes. Omittedscopeis treated as unchanged, not as an error."warn": Emits a warning but continues authentication if scopes are missing."none": Skips scope validation entirely.
- claims_validation
Controls validation of requested claims supplied via the
claimsparameter (OIDC Core §5.5). Whenclaimsincludes entries withessential = TRUEforid_tokenoruserinfo, or explicitvalue/valuesconstraints for individual claims, this setting determines what happens if the returned ID token or userinfo response does not satisfy those requests."none"(default): Skips claims validation entirely. If you leave this default while requestingessential,value, orvaluesconstraints,oauth_client()warns because providers may still complete login without satisfying those claim requests."warn": Emits a warning but continues authentication if requested essential claims are missing or requested claim values are not satisfied."strict": Throws an error if any requested essential claims are missing or requested claimvalue/valuesconstraints are not satisfied by the response.
Enforceable requests under
claims$id_tokenrequire a validated ID token. Configure the provider withid_token_validation = TRUEoruse_nonce = TRUEso shinyOAuth validates the ID token before checking those claims.- userinfo_jwt_required_temporal_claims
Optional character vector of temporal JWT claims that must be present when the UserInfo response is a signed JWT (
application/jwt). Allowed values are"exp","iat", and"nbf".Default is
character(0), which means these claims are validated only when present. Set, for example,userinfo_jwt_required_temporal_claims = "exp"to require an expiry on signed UserInfo JWTs, or pass multiple values to require additional temporal claims.- 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. Whenoauth_module_server()later performs proactive refresh, it also forwards this setting so refreshed access tokens are introspected through the same client policy. 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 a validated 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
)
}
}