--- title: "Usage" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Usage} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ## Overview 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](authentication-flow.html). ## Provider, client, and token objects 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. ## GitHub app registration 1. Register an OAuth App in [GitHub's developer settings](https://github.com/settings/developers). Set both the homepage URL and authorization callback URL to `http://127.0.0.1:8100` for this local example. 2. 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: ```text GITHUB_OAUTH_CLIENT_ID=your-client-id GITHUB_OAUTH_CLIENT_SECRET=your-client-secret ``` Restart R after saving so the environment variables become available. ## Minimal Shiny module example 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](#posit-connect-and-connect-cloud). ```{r, eval = FALSE} 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. ## Authentication state and user information `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](audit-logging.html) records the underlying operations. The token is an S7 object: access its properties with `@`, as in `auth[["token"]]@userinfo`. See [`OAuthToken`](https://lukakoning.github.io/shinyOAuth/reference/OAuthToken.html) for the available properties. `req(auth[["authenticated"]])` suspends the example's output while login is incomplete. ## Manual login and logout buttons The default is to start login automatically. To let users choose when to sign in, replace the example's UI and server with: ```{r, eval = FALSE} ui <- oauth_ui(fluidPage( actionButton("login", "Sign in"), actionButton("logout", "Sign out"), textOutput("status") ), id = "auth", client = client) server <- function(input, output, session) { auth <- oauth_module_server("auth", client, auto_redirect = FALSE) observeEvent(input[["login"]], auth[["request_login"]]()) observeEvent(input[["logout"]], auth[["logout"]]()) output[["status"]] <- renderText({ if (isTRUE(auth[["authenticated"]])) { paste("Signed in as", auth[["token"]]@userinfo[["login"]]) } else { "You are signed out. Use Sign in to continue." } }) } ``` `auth[["logout"]]()` clears the app's local login and attempts to revoke its tokens if the provider supports revocation. It does not sign the user out of their GitHub, Google, or other provider account. The provider may therefore remember them on their next visit. ## Authenticated API requests 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()`: ```{r, eval = FALSE} 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](example-spotify.html). The [request-helper reference](https://lukakoning.github.io/shinyOAuth/reference/perform_resource_req.html) covers prepared requests and transport settings. ## Asynchronous execution 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()`: ```{r, eval = FALSE} 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: ```{r, eval = FALSE} 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()`](https://lukakoning.github.io/shinyOAuth/reference/oauth_module_server.html) for advanced exceptions and the [options reference](package-options.html) for timeouts and retries. ## Token expiry and session length 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. ## Provider configuration and OIDC discovery 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: ```{r, eval = FALSE} 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](authentication-flow.html). ## Deployment 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. ### Callback and browser behavior {#security-checklist} `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](advanced-security.html#browser-binding-and-deployment-boundary) describes the browser and hosting boundaries in more detail. ## Advanced deployment ### Posit Connect and Connect Cloud Test OAuth login in a new browser tab or window, outside the dashboard's embedded app preview. 1. On Connect Cloud, copy the app's sharing URL from Settings > URL, such as `https://.share.connect.posit.cloud`, or your configured custom URL. See the [URL settings guide](https://docs.posit.co/connect-cloud/user/manage/content_settings.html#url). On Posit Connect, use the app's direct content URL. 2. Paste that URL into a new browser tab or window and start login there. The administrative page at `connect.posit.cloud/.../content/...` is not the direct app URL or an OAuth callback URL. 3. Set `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. ### Multiple R processes 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()](https://lukakoning.github.io/shinyOAuth/reference/custom_cache.html) to connect an appropriate backend, following its contract for POST callbacks and hosted Request Objects. ## Local Keycloak development For a local Keycloak server using HTTP, opt in before creating the provider: ```{r, eval = FALSE} 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. ## Further configuration The [`oauth_module_server()` examples](https://lukakoning.github.io/shinyOAuth/reference/oauth_module_server.html#ref-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](https://lukakoning.github.io/shinyOAuth/reference/oauth_provider.html#ref-examples) cover manual OAuth/OIDC setup, discovery, and named providers; the [Microsoft example](https://lukakoning.github.io/shinyOAuth/reference/oauth_provider_microsoft.html#ref-examples) also shows authentication-state summaries and error handling. - [Advanced security](advanced-security.html): form POST callbacks, certificates (mTLS), signed requests (JAR) and responses (JARM), pushed requests (PAR), and tokens tied to a key (DPoP). - [Authentication flow](authentication-flow.html): what the module checks and why. - [Package options](package-options.html): logging, network limits, and debugging settings. - [OpenTelemetry](opentelemetry.html): exporting logs and timing information.