Use shinyOAuth to let users sign in with an existing service and grant your app permission to call its API, without collecting their password. OAuth controls API access; OpenID Connect (OIDC) adds identity checks so the app can verify who signed in.
In R, configure the service and your app registration, then add the
Shiny UI wrapper and server module. The module manages login and tokens;
oauth_ui() handles the browser’s return to the app.
The examples use a GitHub OAuth App. Install shinyOAuth with
install.packages("shinyOAuth"). For the protocol flow and
validation rules, see Authentication
flow.
A provider describes the service users sign in with. A client describes your app’s registration with that service. After login, a token holds the credentials your app uses to call the service and any available user information.
| Concept | In your app |
|---|---|
| Provider | A helper such as oauth_provider_github() supplies the
service’s settings. |
| Client | oauth_client() combines those settings with your app’s
credentials and permissions. |
| Token | auth[["token"]] contains the result of a successful
login. |
The redirect URI, also called the callback URL, is where the browser
returns after login. Register the same address with the provider and in
oauth_client(). Scopes name the permissions to request,
such as read:user; the provider defines which names are
available.
Create your provider and client outside server() so they
remain available when the browser returns from login. Create the module
inside server() so each user has their own login state.
Register an OAuth App in GitHub’s developer
settings. Set both the homepage URL and authorization callback URL
to http://127.0.0.1:8100 for this local example.
Store the app’s client ID and client secret in your R
environment. You can open your user .Renviron with
file.edit(path.expand("~/.Renviron")) and add:
GITHUB_OAUTH_CLIENT_ID=your-client-id
GITHUB_OAUTH_CLIENT_SECRET=your-client-secret
Restart R after saving so the environment variables become available.
Save the following code as app.R and run it. Open
http://127.0.0.1:8100 in a regular browser. Use the
registered address; switching between localhost and
127.0.0.1 can interrupt login.
Deploying to Posit Connect or Connect Cloud? Test login at the direct app URL in a new browser tab or window. The dashboard’s embedded preview can prevent OAuth redirects. See Posit deployment instructions.
library(shiny)
library(shinyOAuth)
# Configure these once, outside server().
provider <- oauth_provider_github()
client <- oauth_client(
provider = provider,
client_id = Sys.getenv("GITHUB_OAUTH_CLIENT_ID"),
client_secret = Sys.getenv("GITHUB_OAUTH_CLIENT_SECRET"),
redirect_uri = "http://127.0.0.1:8100",
scopes = c("read:user", "user:email")
)
ui <- oauth_ui(fluidPage(
h2("My app"),
textOutput("greeting")
), id = "auth", client = client)
server <- function(input, output, session) {
auth <- oauth_module_server("auth", client)
output[["greeting"]] <- renderText({
req(auth[["authenticated"]])
paste("Hello,", auth[["token"]]@userinfo[["login"]])
})
}
runApp(shinyApp(ui, server), port = 8100, launch.browser = FALSE)The browser opens GitHub’s login or permission page, then returns to your app and displays your GitHub username. Use a regular browser: IDE viewers may prevent the redirects needed for login.
auth is a Shiny reactiveValues object. Read
it inside render*(), reactive(), or
observe*(), as you would read other reactive values.
auth[["authenticated"]] tells you whether login passed
the configured checks.auth[["token"]]@userinfo contains the user’s profile,
when fetched. Fields depend on the provider: GitHub uses
login for the username.auth[["error"]] and
auth[["error_description"]] describe a failed login. Audit logging records the underlying
operations.The token is an S7 object: access its properties with @,
as in auth[["token"]]@userinfo. See OAuthToken
for the available properties.
req(auth[["authenticated"]]) suspends the example’s
output while login is incomplete.
Once the user has granted permission, your app can request data from
the service. perform_resource_req() attaches the access
token for you. Add tableOutput("repositories") to the UI
and this output to server():
output[["repositories"]] <- renderTable({
req(auth[["authenticated"]])
repos <- tryCatch({
response <- perform_resource_req(
auth[["token"]],
"https://api.github.com/user/repos",
query = list(per_page = 10)
)
httr2::resp_check_status(response)
httr2::resp_body_json(response, simplifyVector = TRUE)
}, error = function(e) NULL)
validate(need(!is.null(repos), "Could not load repositories. Try again later."))
validate(need(length(repos) > 0, "No repositories to show."))
repos[, c("name", "private"), drop = FALSE]
})This example loads one page; further pages depend on the API. For another complete app, see the Spotify example. The request-helper reference covers prepared requests and transport settings.
By default, the module’s network requests run in the app’s R process,
so a slow provider can block other sessions sharing that process. To run
those requests in background R processes, install mirai and
promises, then configure workers before
server():
mirai::daemons(2)
shiny::onStop(function() mirai::daemons(0))
server <- function(input, output, session) {
auth <- oauth_module_server("auth", client, async = TRUE)
# Add your outputs and observers here.
}Alternatively, configure
future::plan(future::multisession, workers = 2) and use
async = TRUE. This backend requires promises
version 1.3.3 or newer. If both backends are configured, mirai takes
priority. future::sequential() runs in the same R process
and does not avoid blocking.
For an app using future instead of mirai, configure its worker plan before starting the server and release the workers when the app stops:
future::plan(future::multisession, workers = 2)
shiny::onStop(function() future::plan(future::sequential))
server <- function(input, output, session) {
auth <- oauth_module_server("auth", client, async = TRUE)
# Add your outputs and observers here.
}async = TRUE covers the module’s operations. API calls
in your own outputs still run where you call them, and discovery during
app setup stays synchronous. See oauth_module_server()
for advanced exceptions and the options
reference for timeouts and retries.
Access tokens usually expire. If the provider supplies a refresh
token, the module can obtain a replacement before expiry with
refresh_proactively = TRUE. Otherwise, users need to sign
in again when their token expires.
Use reauth_after_seconds to set a maximum time since
interactive login; refreshing a token does not restart that timer. For
OIDC, the module also requests a fresh provider login and checks its
time. OAuth-only providers can only be given an ordinary authorization
request.
indefinite_session = TRUE retains local login state
after token expiry or refresh failure, and disables the
reauth_after_seconds limit.
Use a built-in helper when available, such as
oauth_provider_google(),
oauth_provider_microsoft(), or
oauth_provider_keycloak(). Each helper’s help page
describes its setup. For an OpenID Connect service, discovery reads its
published settings:
provider <- oauth_provider_oidc_discover(
issuer = "https://login.example.com"
)
client <- oauth_client(
provider = provider,
client_id = Sys.getenv("OAUTH_CLIENT_ID"),
client_secret = Sys.getenv("OAUTH_CLIENT_SECRET"),
redirect_uri = "https://my-app.example.com",
scopes = c("openid", "profile", "email")
)Replace the example URLs and credentials with your own registration.
An issuer is the provider’s identifier URL; copy it from the provider’s
configuration. Discovery makes a network request, so run it during app
setup. If your registration specifies a client authentication method,
supply the matching token_auth_style to the provider
helper. Discovery lists the service’s capabilities; your registration
determines which method and credentials to use.
For OIDC, read validated identity from
auth[["token"]]@id_token_claims and check
auth[["token"]]@id_token_validated. GitHub and Spotify
instead fetch profiles through their own APIs. An access token alone
does not verify identity; see Authentication flow.
Replace the local callback URL with the app’s public HTTPS URL, and register that same URL with the provider. Open the app directly in a browser tab. An app embedded in another page may not be able to complete login.
oauth_ui() and oauth_form_post_ui() process
callback data before rendering application UI, with no-store and
no-referrer response headers. The server module manages credentials and
browser binding automatically. The advanced
security guide describes the browser and hosting boundaries in more
detail.
Test OAuth login in a new browser tab or window, outside the dashboard’s embedded app preview.
https://<content-id>.share.connect.posit.cloud, or
your configured custom URL. See the URL
settings guide. On Posit Connect, use the app’s direct content
URL.connect.posit.cloud/.../content/... is not the direct app
URL or an OAuth callback URL.redirect_uri to the exact public callback URL
registered with your provider. In the minimal example this is the app
URL. With a dedicated route such as /callback, retain that
callback path in the registration, but open the app’s entry URL to start
login.A login can start on one R process and return to another. Those
processes need the same provider/client settings, a shared pending-login
store, and the same secret state_key to read the saved
details. Load that key from deployment secret storage; it needs at least
32 random bytes.
The shared store must read and remove a pending record as one operation, so two processes cannot accept the same login response. Plain disk caching does not provide that guarantee. Use custom_cache() to connect an appropriate backend, following its contract for POST callbacks and hosted Request Objects.
For a local Keycloak server using HTTP, opt in before creating the provider:
options(shinyOAuth.allow_insecure_oidc_loopback = TRUE)
provider <- oauth_provider_keycloak(
base_url = "http://localhost:8080", realm = "shinyoauth"
)This option is for local development only. Production provider URLs need HTTPS. The ordinary HTTP host option allows local app addresses; it does not relax OIDC discovery.
The oauth_module_server()
examples include complete apps for automatic login, a manual login
button, and fetching GitHub repositories with the user’s access token.
The oauth_provider()
examples cover manual OAuth/OIDC setup, discovery, and named
providers; the Microsoft
example also shows authentication-state summaries and error
handling.