Skip to contents

Accept the provider's login callback as an HTTP POST and continue login with oauth_module_server(). Use this when you select response_mode = "form_post" or "form_post.jwt", either because the provider requires it or to keep callback parameters out of the browser URL. The POST arrives before a Shiny session exists; this wrapper receives it and makes the validated callback available to the server module. For query-string callbacks, use oauth_ui().

Usage

oauth_form_post_ui(
  base_ui,
  id = NULL,
  client = NULL,
  callback_path = NULL,
  request_uri_resolver = NULL,
  clients = NULL
)

Arguments

base_ui

Existing Shiny UI object, or a UI function accepting req.

id

Shiny module id used by oauth_module_server(). This must match the id argument passed to the server module.

client

OAuthClient object used by oauth_module_server().

callback_path

Optional URL path to accept POST callbacks on. Defaults to the path component of client@redirect_uri and must match it when supplied. This is the public callback path; use request_uri_resolver to map a trusted proxy's backend path to the registered public URI.

request_uri_resolver

Optional function accepting the Rook req environment and returning the trusted, public absolute request URI without relying on query parameters. Return NULL to reject the route. This is intended for HTTPS-terminating proxies whose backend request has an HTTP Rook scheme. The function must verify the proxy trust boundary before using forwarded headers; its result is still required to match the configured redirect origin and callback_path. Registered fixed query parameters must also occur unchanged in the incoming request. Continuation URLs preserve only registered application parameters.

clients

Optional client registry as in oauth_ui(). With a registry, omit id, client, and callback_path; each client's redirect URI sets its route, and both query and form-post clients are supported.

Value

A Shiny UI function. Pass it to shiny::shinyApp() and, for non-root callback paths, use uiPattern = ".*" so Shiny routes the callback path to this UI function.

Details

Set response_mode on your oauth_client(), wrap your UI here, and use the same id and client in oauth_module_server(). The wrapper includes the browser setup and privacy header supplied by oauth_ui(). For a callback path below the app root, pass uiPattern = ".*" to shiny::shinyApp() so Shiny routes the callback to this wrapper.

The wrapper checks the incoming address and login state, stores the callback temporarily, and redirects the browser to a normal Shiny page with a single-use handle. Raw callback values do not appear in that redirected URL. For "form_post.jwt", it also validates the signed response using JWT Secured Authorization Response Mode (JARM). Every POST receives its own random handle. Handles expire after 120 seconds or the smaller of state_payload_max_age and the state store lifetime. The module still verifies the browser binding before consuming login state. Each client/provider/module namespace has a separate bounded pool in the state store: 256 partitions of eight slots, with each login assigned to one partition. When a partition is full, a new POST replaces its oldest response. An expired or replaced handle fails validation; it can never select the replacement response. Completed logins invalidate their remaining candidates and remove locally known candidates where possible. Shared stores still require atomic take. With set_if_absent, slots are claimed atomically with a short TTL; a full partition rejects new callbacks until a slot is consumed or expires. Otherwise, concurrent writers can also replace a candidate; handle validation fails closed in that case. Pending form-post handles issued by an older version must be restarted after upgrading.

Deployment behind a proxy

If a proxy receives HTTPS and forwards HTTP to Shiny, supply a request_uri_resolver that reconstructs the public address after verifying the request came from your trusted proxy. The default resolver does not trust forwarded headers. The resulting address must match the configured redirect origin and callback path. See the advanced security vignette.

Callback size limits are documented in the package options reference.

Examples

if (
  # Example requires a local or remote Keycloak realm whose client allows
  # http://127.0.0.1:8100/callback as a valid redirect URI.
  nzchar(Sys.getenv("KEYCLOAK_BASE_URL")) &&
    nzchar(Sys.getenv("KEYCLOAK_REALM")) &&
    nzchar(Sys.getenv("KEYCLOAK_CLIENT_ID")) &&
    interactive()
) {
  library(shiny)
  library(shinyOAuth)

  options(shinyOAuth.allow_insecure_oidc_loopback = TRUE)

  provider <- oauth_provider_keycloak(
    base_url = Sys.getenv("KEYCLOAK_BASE_URL"),
    realm = Sys.getenv("KEYCLOAK_REALM")
  )

  client <- oauth_client(
    provider = provider,
    client_id = Sys.getenv("KEYCLOAK_CLIENT_ID"),
    client_secret = Sys.getenv("KEYCLOAK_CLIENT_SECRET"),
    redirect_uri = "http://127.0.0.1:8100/callback",
    scopes = c("openid", "profile", "email"),
    response_mode = "form_post"
  )

  base_ui <- fluidPage(
    uiOutput("login")
  )

  ui <- oauth_form_post_ui(base_ui, id = "auth", client = client)

  server <- function(input, output, session) {
    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.")
      }
    })
  }

  runApp(
    shinyApp(ui, server, uiPattern = ".*"),
    port = 8100,
    launch.browser = FALSE
  )
}