| Title: | Provider-Agnostic OAuth Authentication for 'shiny' Applications |
| Version: | 0.1.3 |
| 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 |
| RoxygenNote: | 7.3.3 |
| 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.0.0), cachem (≥ 1.1.0), jose (≥ 1.2.0), cli (≥ 3.0.0), htmltools (≥ 0.5.0) |
| Suggests: | testthat (≥ 3.0.0), knitr, rmarkdown, webfakes, promises, future, withr, later, sodium, shinytest2, xml2 |
| 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 |
| NeedsCompilation: | no |
| Packaged: | 2025-11-10 11:15:42 UTC; Luka |
| Author: | Luka Koning [aut, cre, cph] |
| Maintainer: | Luka Koning <koningluka@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2025-11-10 13: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]
See Also
Useful links:
Report bugs at https://github.com/lukakoning/shinyOAuth/issues
OAuthClient S7 class
Description
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_,
redirect_uri = character(0),
scopes = character(0),
state_store = cachem::cache_mem(max_age = 300),
state_entropy = 64,
state_key = random_urlsafe(n = 128)
)
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 |
redirect_uri |
Redirect URI registered with provider |
scopes |
Vector of scopes to request |
state_store |
State storage backend. Defaults to Trade-offs: The client automatically generates, persists (in |
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, you must configure a shared |
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... "
)
)
}
# 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)
}
# 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)
}
# 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)
}
}
OAuthProvider S7 class
Description
S7 class representing an OAuth 2.0 provider configuration. Includes endpoints, OIDC settings, and various security options which govern the OAuth and OIDC flows.
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_,
issuer = NA_character_,
use_nonce = FALSE,
use_pkce = TRUE,
pkce_method = "S256",
userinfo_required = FALSE,
userinfo_id_selector = function(userinfo) userinfo$sub,
userinfo_id_token_match = FALSE,
id_token_required = FALSE,
id_token_validation = FALSE,
extra_auth_params = list(),
extra_token_params = list(),
extra_token_headers = character(0),
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", "PS256", "PS384", "PS512", "ES256",
"ES384", "ES512", "EdDSA"),
allowed_token_types = character(0),
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) |
issuer |
OIDC issuer URL (optional; required for ID token validation).
This is the base URL that identifies the OpenID Provider (OP). It is used
during ID token validation to verify the |
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. "plain" should only be used for non-compliant providers that do 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 when |
userinfo_id_token_match |
Whether to verify that the user ID ("sub") from the ID token
matches the user ID extracted from the userinfo response. This requires both
For |
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 requires the provider to be a valid OpenID Connect provider and may require setting the client's scope to include "openid". 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 token exchange requests (named character vector) |
token_auth_style |
How to authenticate when exchanging tokens. One of:
|
jwks_cache |
JWKS cache backend. If not provided, a TTL guidance: Choose Cache keys are internal, hashed by issuer and pinning configuration. Cache values are
lists with elements |
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. Takes precedence over |
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). When non-empty, the
token response MUST 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)
# 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)
slack_provider <- oauth_provider_slack()
# Keycloak
# (requires configured Keycloak realm; example below is therefore not run)
## Not run:
oauth_provider_keycloak(base_url = "http://localhost:8080", realm = "myrealm")
## End(Not run)
# Auth0
# (requires configured Auth0 domain; example below is therefore not run)
## Not run:
oauth_provider_auth0(domain = "your-tenant.auth0.com")
## End(Not run)
# Okta
# (requires configured Okta domain; example below is therefore not run)
## Not run:
oauth_provider_okta(domain = "dev-123456.okta.com")
## End(Not run)
OAuthToken S7 class
Description
S7 class representing OAuth tokens and (optionally) user information.
Usage
OAuthToken(
access_token = character(0),
refresh_token = NA_character_,
id_token = NA_character_,
expires_at = Inf,
userinfo = list()
)
Arguments
access_token |
Access token |
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) |
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)
## Not run:
# 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)
## End(Not run)
Build an authorized httr2 request with Bearer token
Description
Convenience helper to reduce boilerplate when calling downstream APIs.
It creates an httr2::request() for the given URL, attaches the
Authorization: Bearer <token> header, and applies the package's standard
HTTP defaults (timeout and User-Agent).
Accepts either a raw access token string or an OAuthToken object.
Usage
client_bearer_req(token, url, method = "GET", headers = NULL, query = 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". |
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. |
Value
An httr2 request object, ready to be further customized or
performed with httr2::req_perform().
Examples
# Make request using OAuthToken object
# (code is not run because it requires a real token from user interaction)
## Not run:
# Get an OAuthToken
# (typically provided as reactive return value by `oauth_module_server()`)
token <- OAuthToken()
# Build request
request <- client_bearer_req(
token,
"https://api.example.com/resource",
query = list(limit = 5)
)
# Perform request
response <- httr2::req_perform(request)
## End(Not run)
Create a custom cache backend (cachem-like)
Description
Builds a minimal cachem-like cache backend object that exposes cachem-compatible methods:
$get(key, missing), $set(key, value), $remove(key), and $info().
Use this helper when you want to plug a custom state store or JWKS cache
into 'shinyOAuth', when cachem::cache_mem() or cachem::cache_disk()
are not suitable. This may be useful specifically when you deploy
a Shiny app to a multi-process environment 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)
The $info() method is optional, but if provided and it returns a list with
max_age (seconds), shinyOAuth will align cookie/issued_at TTLs to that value.
Usage
custom_cache(get, set, remove, 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) -> logical or sentinel. Required. For state stores, this enforces single-use eviction. If your backend performs
an atomic "get-and-delete" (e.g., SQL DELETE .. RETURNING), you may supply
a function which does nothing here but returns Recommended contract for interoperability and strong replay protection:
When the return value is not |
info |
Function() -> list(max_age = seconds, ...). Optional This may be provided to because 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)
return(TRUE) # signal successful deletion
}
return(TRUE) # key did not exist
},
info = function() list(max_age = 600)
)
Throw an error if any safety checks have been disabled
Description
This function checks if any safety checks have been disabled via options intended for local development use only. If any such options are detected, an error is thrown to prevent accidental use in production environments.
Usage
error_on_softened()
Details
It checks for the following options:
-
shinyOAuth.skip_browser_token: Skips browser cookie presence check -
shinyOAuth.skip_id_sig: Skips ID token signature verification -
shinyOAuth.print_errors: Enables printing of error messages -
shinyOAuth.print_traceback: Enables printing of tracebacks (opt-in only; default FALSE) -
shinyOAuth.expose_error_body: Exposes HTTP response bodies
Note: Tracebacks are only treated as a "softened" behavior when the
shinyOAuth.print_traceback option is explicitly set to TRUE.
The default is FALSE, even in interactive or test sessions.
Value
Invisible TRUE if no safety checks are disabled; otherwise, an error is thrown.
Examples
# Throw an error if any developer-only softening options are enabled
# Below call does not error if run with default options:
error_on_softened()
# Below call would error (is therefore not run):
## Not run:
options(shinyOAuth.skip_id_sig = TRUE)
error_on_softened()
## End(Not run)
Get user info from OAuth 2.0 provider
Description
Fetches user information from the provider's userinfo endpoint using the provided access token. Emits an audit event with redacted details.
Usage
get_userinfo(oauth_client, token)
Arguments
oauth_client |
OAuthClient object. The client must have a
|
token |
Either an OAuthToken object or a raw access token string. |
Value
A list containing the user information as 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)
## Not run:
# 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)
## End(Not run)
Handle OAuth 2.0 callback: verify state, swap code for token, verify token
Description
Handle OAuth 2.0 callback: verify state, swap code for token, verify token
Usage
handle_callback(
oauth_client,
code,
payload,
browser_token,
decrypted_payload = NULL,
state_store_values = NULL
)
Arguments
oauth_client |
An OAuthClient object representing the OAuth client configuration. |
code |
The authorization code received from the OAuth provider during the callback. |
payload |
The encrypted state payload received from the OAuth provider during the callback
(this should be the same value that was generated and sent in |
browser_token |
Browser token present in the user's session (this is managed
by |
decrypted_payload |
Optional pre-decrypted and validated payload list
(as returned by |
state_store_values |
Optional pre-fetched state store entry (a list with
|
Value
An OAuthToken' object containing the access token, refresh token, expiration time, user information (if requested), and ID token (if applicable). If any step of the process fails (e.g., state verification, token exchange, token validation), an error is thrown indicating the failure reason.
Examples
# Please note: `prepare_callback()` & `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_callback()` and `handle_callback()`
# (code is not run because it would require user interaction)
## Not run:
# 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_callback(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)
## End(Not run)
Introspect an OAuth 2.0 token
Description
Introspects an access or refresh token using RFC 7662 when the
provider exposes an introspection endpoint. Returns a list including at least
supported (logical) and active (logical|NA) and the parsed response (if
any) under raw.
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."client_secret_jwt" / "private_key_jwt": a signed JWT client assertion is generated (RFC 7523) and sent via
client_assertion_typeandclient_assertion, withaudset to the provider'sintrospection_url.
Usage
introspect_token(
oauth_client,
oauth_token,
which = c("access", "refresh"),
async = FALSE
)
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 promises is available, run in background and return a promise resolving to the result list |
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"). 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, active, raw, status
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)
## Not run:
# 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)
## End(Not run)
Check if URL(s) are HTTPS and/or in allowed hosts lists
Description
Returns TRUE if every input URL is 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 thus an allowlist of hosts/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 scheme rules),
but HTTPS is still required unless the host is in allowed_non_https_hosts.
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 be used elsewhere to test if URLs would be allowed. Internally, it will always
determine the default values for allowed_non_https_hosts and allowed_hosts
from the options shinyOAuth.allowed_non_https_hosts and
shinyOAuth.allowed_hosts, respectively.
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
Create generic OAuthClient
Usage
oauth_client(
provider,
client_id = Sys.getenv("OAUTH_CLIENT_ID"),
client_secret = Sys.getenv("OAUTH_CLIENT_SECRET"),
redirect_uri,
scopes = character(0),
state_store = cachem::cache_mem(max_age = 300),
state_entropy = 64,
state_key = random_urlsafe(128),
client_private_key = NULL,
client_private_key_kid = NULL,
client_assertion_alg = NULL
)
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 |
scopes |
Vector of scopes to request |
state_store |
State storage backend. Defaults to Trade-offs: The client automatically generates, persists (in |
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, you must 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 |
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... "
)
)
}
# 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)
}
# 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)
}
# 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)
}
}
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,
tab_title_cleaning = TRUE,
tab_title_replacement = 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, performs token exchange and refresh in the background
using the promises package (future_promise), and updates values when the
promise resolves. Requires the promises::promises package and a suitable
backend to be 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 |
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 |
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
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 = TRUEso network calls run in a background future via the promises package (configure a multisession/multicore backend), 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 and the state payload'sissued_atvalidation window.Watchdog for missing browser token: to catch misconfiguration early during development, the module includes a short watchdog. If the browser token cookie is not set within 1500ms of module initialization, a warning is emitted to the R console. This likely means you forgot to include
use_shinyOAuth()in your UI, but it may also indicate that a user of your app is using a browser with JavaScript disabled. The watchdog prints a warning only once per R session, but if you want to suppress it permanently, you can setoptions(shinyOAuth.disable_watchdog_warning = TRUE).
Value
A reactiveValues object with token, error, error_description,
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. -
token: OAuthToken object, or NULL if not yet authenticated. This contains the access token, refresh token (if any), ID token (if any), and userinfo (if fetched). See OAuthToken for details. Note that since OAuthToken is a S7 object, you access its fields with@, e.g.,token@userinfo. -
error: error code string when the OAuth flow fails. Be careful with exposing this directly to users, as it may contain sensitive information which could aid an attacker. -
error_description: human-readable error detail when available. Be extra careful with exposing this directly to users, as it may contain even more sensitive information which could aid an attacker. -
browser_token: internal opaque browser cookie value; used for state double-submit protection; NULL if not yet set -
pending_callback: internal list(code, state); used to defer token exchange 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 implement a manual login flow
(e.g., with your own 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. This is the main entry point for login whenauto_redirect = FALSEand you want to trigger login from your own UI -
logout(): clears the current token settingauthenticatedto FALSE, and clears 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 (verify withhas_browser_token()first). 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... "
)
)
}
# 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)
}
# 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)
}
# 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)
}
}
Create generic OAuthProvider
Description
Helper function to create an OAuthProvider object. This function provides sensible defaults and infers some settings based on the provided parameters.
Usage
oauth_provider(
name,
auth_url,
token_url,
userinfo_url = NA_character_,
introspection_url = NA_character_,
issuer = NA_character_,
use_nonce = NULL,
use_pkce = TRUE,
pkce_method = "S256",
userinfo_required = NULL,
userinfo_id_token_match = NULL,
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", "PS256", "PS384", "PS512", "ES256",
"ES384", "ES512", "EdDSA"),
allowed_token_types = NULL,
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) |
issuer |
OIDC issuer URL (optional; required for ID token validation).
This is the base URL that identifies the OpenID Provider (OP). It is used
during ID token validation to verify the |
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. "plain" should only be used for non-compliant providers that do 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 verify that the user ID ("sub") from the ID token
matches the user ID extracted from the userinfo response. This requires both
For |
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 when |
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 requires the provider to be a valid OpenID Connect provider and may require setting the client's scope to include "openid". 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 token exchange requests (named character vector) |
token_auth_style |
How to authenticate when exchanging tokens. One of:
|
jwks_cache |
JWKS cache backend. If not provided, a TTL guidance: Choose Cache keys are internal, hashed by issuer and pinning configuration. Cache values are
lists with elements |
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. Takes precedence over |
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). When non-empty, the
token response MUST include |
leeway |
Clock skew leeway (seconds) applied to ID token |
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)
# 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)
slack_provider <- oauth_provider_slack()
# Keycloak
# (requires configured Keycloak realm; example below is therefore not run)
## Not run:
oauth_provider_keycloak(base_url = "http://localhost:8080", realm = "myrealm")
## End(Not run)
# Auth0
# (requires configured Auth0 domain; example below is therefore not run)
## Not run:
oauth_provider_auth0(domain = "your-tenant.auth0.com")
## End(Not run)
# Okta
# (requires configured Okta domain; example below is therefore not run)
## Not run:
oauth_provider_okta(domain = "dev-123456.okta.com")
## End(Not run)
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 to request in auth flows |
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)
# 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)
slack_provider <- oauth_provider_slack()
# Keycloak
# (requires configured Keycloak realm; example below is therefore not run)
## Not run:
oauth_provider_keycloak(base_url = "http://localhost:8080", realm = "myrealm")
## End(Not run)
# Auth0
# (requires configured Auth0 domain; example below is therefore not run)
## Not run:
oauth_provider_auth0(domain = "your-tenant.auth0.com")
## End(Not run)
# Okta
# (requires configured Okta domain; example below is therefore not run)
## Not run:
oauth_provider_okta(domain = "dev-123456.okta.com")
## End(Not run)
Create a GitHub OAuthProvider
Description
Pre-configured OAuth 2.0 provider 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)
# 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)
slack_provider <- oauth_provider_slack()
# Keycloak
# (requires configured Keycloak realm; example below is therefore not run)
## Not run:
oauth_provider_keycloak(base_url = "http://localhost:8080", realm = "myrealm")
## End(Not run)
# Auth0
# (requires configured Auth0 domain; example below is therefore not run)
## Not run:
oauth_provider_auth0(domain = "your-tenant.auth0.com")
## End(Not run)
# Okta
# (requires configured Okta domain; example below is therefore not run)
## Not run:
oauth_provider_okta(domain = "dev-123456.okta.com")
## End(Not run)
Create a Google OAuthProvider
Description
Pre-configured OAuthProvider 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)
# 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)
slack_provider <- oauth_provider_slack()
# Keycloak
# (requires configured Keycloak realm; example below is therefore not run)
## Not run:
oauth_provider_keycloak(base_url = "http://localhost:8080", realm = "myrealm")
## End(Not run)
# Auth0
# (requires configured Auth0 domain; example below is therefore not run)
## Not run:
oauth_provider_auth0(domain = "your-tenant.auth0.com")
## End(Not run)
# Okta
# (requires configured Okta domain; example below is therefore not run)
## Not run:
oauth_provider_okta(domain = "dev-123456.okta.com")
## End(Not run)
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., "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), "private_key_jwt", or "client_secret_jwt". Defaults
to "body" for Keycloak, which works for both confidential clients and
public PKCE clients (secretless). If you pass |
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)
# 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)
slack_provider <- oauth_provider_slack()
# Keycloak
# (requires configured Keycloak realm; example below is therefore not run)
## Not run:
oauth_provider_keycloak(base_url = "http://localhost:8080", realm = "myrealm")
## End(Not run)
# Auth0
# (requires configured Auth0 domain; example below is therefore not run)
## Not run:
oauth_provider_auth0(domain = "your-tenant.auth0.com")
## End(Not run)
# Okta
# (requires configured Okta domain; example below is therefore not run)
## Not run:
oauth_provider_okta(domain = "dev-123456.okta.com")
## End(Not run)
Create a Microsoft (Entra ID) OAuthProvider
Description
Pre-configured OAuthProvider 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 (no discovery).
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
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 will enable strict ID token
validation (issuer match). When using the multi-tenant aliases ("common",
"organizations", "consumers"), the exact issuer depends on the account that
signs in and therefore ID token validation is disabled by default to avoid
false negatives. You can override this via id_token_validation if you know
the environment guarantees a fixed issuer.
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
'9391afd1-7129-4938-9e4d-633c688f93c0' (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 (str):\n",
paste(capture.output(str(tok)), collapse = "\n")
)
})
output$user_info <- renderPrint({
req(auth$token)
auth$token@userinfo
})
output$oauth_error <- renderUI({
if (!is.null(auth$error)) {
msg <- auth$error
if (!is.null(auth$error_description)) {
msg <- paste0(msg, ": ", auth$error_description)
}
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
Preconfigured OAuthProvider for OpenID Connect (OIDC) compliant providers.
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 (or a subdomain). 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)
# 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)
slack_provider <- oauth_provider_slack()
# Keycloak
# (requires configured Keycloak realm; example below is therefore not run)
## Not run:
oauth_provider_keycloak(base_url = "http://localhost:8080", realm = "myrealm")
## End(Not run)
# Auth0
# (requires configured Auth0 domain; example below is therefore not run)
## Not run:
oauth_provider_auth0(domain = "your-tenant.auth0.com")
## End(Not run)
# Okta
# (requires configured Okta domain; example below is therefore not run)
## Not run:
oauth_provider_okta(domain = "dev-123456.okta.com")
## End(Not run)
Discover and create an OpenID Connect (OIDC) OAuthProvider
Description
Uses the OpenID Connect discovery document at
/.well-known/openid-configuration to auto-configure an OAuthProvider.
When present, introspection_endpoint is wired into the resulting provider
for RFC 7662 support.
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", "PS256", "PS384", "PS512", "ES256",
"ES384", "ES512", "EdDSA"),
allowed_token_types = c("Bearer"),
jwks_host_issuer_match = TRUE,
issuer_match = TRUE,
...
)
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) or "body" (client_secret_post). If NULL (default),
it is inferred conservatively from discovery. When PKCE is enabled and the
provider advertises support for public clients via |
allowed_algs |
Character vector of allowed ID token signing algorithms. Defaults to a broad set of common algorithms, including RSA (RS*), RSA-PSS (PS*), 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 (or a subdomain). For
providers that serve JWKS from a different host, set
|
issuer_match |
Logical, default TRUE. When TRUE, requires the discovery
issuer's scheme/host to match the input |
... |
Additional fields passed to |
Details
ID token algorithms: by default this helper accepts common asymmetric algorithms RSA (RS*), RSA-PSS (PS*), 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(with PKCE), as well as JWT-based methodsprivate_key_jwtandclient_secret_jwtper RFC 7523.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 only uses publicnonefor 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 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. 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)
# 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)
slack_provider <- oauth_provider_slack()
# Keycloak
# (requires configured Keycloak realm; example below is therefore not run)
## Not run:
oauth_provider_keycloak(base_url = "http://localhost:8080", realm = "myrealm")
## End(Not run)
# Auth0
# (requires configured Auth0 domain; example below is therefore not run)
## Not run:
oauth_provider_auth0(domain = "your-tenant.auth0.com")
## End(Not run)
# Okta
# (requires configured Okta domain; example below is therefore not run)
## Not run:
oauth_provider_okta(domain = "dev-123456.okta.com")
## End(Not run)
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)
# 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)
slack_provider <- oauth_provider_slack()
# Keycloak
# (requires configured Keycloak realm; example below is therefore not run)
## Not run:
oauth_provider_keycloak(base_url = "http://localhost:8080", realm = "myrealm")
## End(Not run)
# Auth0
# (requires configured Auth0 domain; example below is therefore not run)
## Not run:
oauth_provider_auth0(domain = "your-tenant.auth0.com")
## End(Not run)
# Okta
# (requires configured Okta domain; example below is therefore not run)
## Not run:
oauth_provider_okta(domain = "dev-123456.okta.com")
## End(Not run)
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)
# 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)
slack_provider <- oauth_provider_slack()
# Keycloak
# (requires configured Keycloak realm; example below is therefore not run)
## Not run:
oauth_provider_keycloak(base_url = "http://localhost:8080", realm = "myrealm")
## End(Not run)
# Auth0
# (requires configured Auth0 domain; example below is therefore not run)
## Not run:
oauth_provider_auth0(domain = "your-tenant.auth0.com")
## End(Not run)
# Okta
# (requires configured Okta domain; example below is therefore not run)
## Not run:
oauth_provider_okta(domain = "dev-123456.okta.com")
## End(Not run)
Create a Spotify OAuthProvider
Description
Pre-configured OAuth 2.0 provider for Spotify. Uses /v1/me as "userinfo". No ID token (not OIDC).
Usage
oauth_provider_spotify(
name = "spotify",
scope = "user-read-email user-read-private"
)
Arguments
name |
Optional provider name (default "spotify") |
scope |
Optional space-separated scope string (default "user-read-email user-read-private") |
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)
# 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)
slack_provider <- oauth_provider_slack()
# Keycloak
# (requires configured Keycloak realm; example below is therefore not run)
## Not run:
oauth_provider_keycloak(base_url = "http://localhost:8080", realm = "myrealm")
## End(Not run)
# Auth0
# (requires configured Auth0 domain; example below is therefore not run)
## Not run:
oauth_provider_auth0(domain = "your-tenant.auth0.com")
## End(Not run)
# Okta
# (requires configured Okta domain; example below is therefore not run)
## Not run:
oauth_provider_okta(domain = "dev-123456.okta.com")
## End(Not run)
Prepare a OAuth 2.0 authorization call and build an authorization URL
Description
This function prepares an OAuth 2.0 authorization call by generating necessary state, PKCE, and nonce values, storing them securely, and constructing the authorization URL to redirect the user to. The state and accompanying values are stored in the client's state store for later verification during the callback phase of the OAuth 2.0 flow.
Usage
prepare_call(oauth_client, browser_token)
Arguments
oauth_client |
An OAuthClient object representing the OAuth client configuration. |
browser_token |
A string token (e.g., from a browser cookie) to identify the user/session. |
Value
A string containing the constructed authorization URL. This URL should be used to redirect the user to the OAuth provider's authorization endpoint.
Examples
# Please note: `prepare_callback()` & `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_callback()` and `handle_callback()`
# (code is not run because it would require user interaction)
## Not run:
# 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_callback(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)
## End(Not run)
Refresh an OAuth 2.0 token
Description
Refreshes an OAuth 2.0 access token using a refresh token.
Usage
refresh_token(oauth_client, token, async = FALSE, introspect = FALSE)
Arguments
oauth_client |
OAuthClient object |
token |
OAuthToken object containing the refresh token |
async |
Logical, default FALSE. If TRUE and the |
introspect |
Logical, default FALSE. After a successful refresh, if the provider exposes an introspection endpoint, perform a best-effort introspection of the new access token for audit/diagnostics. The result is not stored on the token object. |
Value
An updated OAuthToken object with a new access token. If the
provider issues a new refresh token, that replaces the old one. When the
provider returns an ID token and id_token_validation = TRUE, it is
validated. When userinfo_required = TRUE, fresh userinfo is fetched and
stored on the token. expires_at is computed from expires_in when
provided; otherwise set to Inf.
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)
## Not run:
# 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)
## End(Not run)
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.
Usage
state_payload_decrypt_validate(client, encrypted_payload)
Arguments
client |
OAuthClient instance |
encrypted_payload |
Encrypted state payload string received via the
|
Value
A named list payload (state, client_id, redirect_uri, scopes,
provider, issued_at) on success; otherwise throws an error via
err_invalid_state().
Fetch and remove the single-use state entry
Description
Retrieves the state-bound values from the client's state_store and removes
the entry to enforce single-use semantics.
Usage
state_store_get_remove(client, state)
Arguments
client |
OAuthClient instance |
state |
Plain (decrypted) state string used as the logical key |
Value
A list with browser_token, pkce_code_verifier, and nonce.
Throws an error via err_invalid_state() if retrieval or removal fails,
or if the retrieved value is missing/malformed.
Add JavaScript dependency to the UI of a Shiny app
Description
Adds the package's client-side JavaScript helpers as an htmlDependency to your Shiny UI. This enables features such as redirection and setting the browser cookie token.
Without adding this to the UI of your app, the oauth_module_server() will not function.
Usage
use_shinyOAuth()
Details
Place this near the top-level of your UI (e.g., inside fluidPage() or
tagList()), similar to how you would use shinyjs::useShinyjs().
Value
A tagList containing a singleton dependency tag that ensures the JS
file inst/www/shinyOAuth.js is loaded
See Also
Examples
ui <- shiny::fluidPage(
use_shinyOAuth(),
# ...
)