---
title: "Use your Shiny login with other R packages"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Use your Shiny login with other R packages}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

## Reuse a login in another package

An R package or database driver may need an access token, a credential for
calling a service on the user's behalf. Reuse the login managed by shinyOAuth
so the user does not need to sign in a second time.

A connection gives your server code access to that login. Ask it for a current
token just before calling the other package; shinyOAuth can refresh it when
needed. In reactive server code, with `auth` from `oauth_module_server()`:

```{r, eval = FALSE}
connection <- shiny::req(auth$connection())
token <- connection$access_token(required_scopes = "records.read")
existing_sdk_read_records(access_token = token)
```

`existing_sdk_read_records()` stands for your package's function. A scope is
a permission name defined by the service; `required_scopes` checks the
permissions needed for this operation. The other package sends its own request.

The examples build on the login setup in [Usage](usage.html).

## A complete Shiny example

This template uses `httr2` so you can see where the token goes. Replace the
placeholder URLs, scopes and credentials with your provider's settings.
The read button loads records; the output checks that the saved result still
belongs to the current login before displaying it.

```{r, eval = FALSE}
library(shiny)
library(shinyOAuth)

provider <- oauth_provider(
  name = "records",
  auth_url = "https://login.example/authorize",
  token_url = "https://login.example/token",
  userinfo_required = FALSE
)
client <- oauth_client(
  provider,
  client_id = Sys.getenv("OAUTH_CLIENT_ID"),
  client_secret = Sys.getenv("OAUTH_CLIENT_SECRET"),
  redirect_uri = "https://app.example/callback",
  scopes = c("records.read", "records.write"),
  required_scopes = "records.read"
)

ui <- oauth_ui(fluidPage(
  actionButton("login", "Sign in"),
  actionButton("again", "Sign in again"),
  actionButton("logout", "Sign out"),
  actionButton("load", "Load records"),
  textOutput("permissions"),
  verbatimTextOutput("records")
), id = "auth", client = client)

server <- function(input, output, session) {
  auth <- oauth_module_server(
    "auth", client, auto_redirect = FALSE, refresh_proactively = TRUE
  )
  observeEvent(input$login, {
    auth$request_login()
    invisible(NULL)
  })
  observeEvent(input$again, {
    auth$reauthorize()
    invisible(NULL)
  })
  observeEvent(input$logout, auth$logout())

  output$permissions <- renderText({
    connection <- req(auth$connection())
    if (connection$has_scopes("records.write")) "Editing available" else "Read only"
  })

  records <- eventReactive(input$load, {
    connection <- req(auth$connection())
    data <- tryCatch({
      response <- httr2::request("https://api.example/records") |>
        httr2::req_auth_bearer_token(
          connection$access_token(required_scopes = "records.read")
        ) |>
        httr2::req_perform()
      httr2::resp_body_json(response)
    }, shinyOAuth_access_error = function(error) {
      showNotification("Authorization unavailable. Try again or sign in again.")
      req(FALSE)
    })
    list(authorization = connection$id, data = data)
  })

  output$records <- renderPrint({
    connection <- req(auth$connection())
    result <- records()
    req(identical(result$authorization, connection$id))
    req(connection$has_scopes("records.read"))
    result$data
  })
}

shinyApp(ui, server, uiPattern = ".*")
```

`uiPattern = ".*"` allows the example's `/callback` route.
`req(auth$connection())` waits for a login, and `$has_scopes()` checks whether
an optional feature is available. The call to `$access_token(required_scopes = ...)`
checks the operation's permissions before returning credentials.

The output compares the saved connection ID and current permissions before
displaying records, so results from an earlier login disappear after logout
or a change of account.

## Refresh and signing in again

`$access_token()` checks the token's remaining lifetime and refreshes when
needed. For a slow operation, use `min_valid_for_seconds` to require more remaining
token lifetime. If no usable token is available, the call raises an error; it does not
open a login page or repeat the other package's operation.

Offer `auth$reauthorize()` when the user needs to sign in again. It preserves
locally reduced permissions and ends the previous local login. Cancelling does
not restore it. An ordinary new login requests the client's original scopes.

Token retrieval follows the module's
[expiry and maximum-login-age settings](usage.html#token-expiry-and-session-length).

## SDK token callbacks and database drivers

Some client libraries, often called SDKs, accept a function that supplies a
token when needed. Build that client inside the user's Shiny session:

```{r, eval = FALSE}
sdk <- reactive({
  connection <- req(auth$connection())
  example_sdk_client(
    token = function() connection$access_token(required_scopes = "records.read")
  )
})
```

`example_sdk_client()` stands for the package's constructor. The reactive
expression builds a client for the current connection and rebuilds it when
login changes; its callback remains bound to that connection.

If a database driver only accepts a token string, acquire it just before opening
or updating the database connection.

Exported bearer tokens carry the provider's full grant, even when the app applies
narrower local permission checks. `$access_token()` rejects DPoP and
certificate-bound tokens; `$request()` supplies the matching proof or
certificate for those requests.

## Several accounts and optional profile retrieval

With a connection manager, choose a saved login with
`auth$connection(connection_id)`. Use `auth$reauthorize(connection_id)` to replace
it; the replacement gets a new connection ID. See
[Multiple authorizations](multiple-authorizations.html).

For one login with separate tokens for several APIs, see
[Token targets](token-targets.html).

For Microsoft APIs other than Graph, configure `userinfo_required = FALSE` on
the provider to skip the Graph profile request. `connection$identity()` reads
selected previously validated identity fields.

## Advanced: connection lifetime and permissions

A connection belongs to one Shiny session, and its methods check that
ownership before resolving credentials. Refresh preserves its ID; logout,
replacement and session closure invalidate it, even if the same account signs
in again.

Reads of `connection()`, `$access_token()` and `$has_scopes()` do not rerun on
refresh alone when login and permissions stay the same. Use `$summary()` for
status, and `auth$touch()` to record explicit user activity for a retained login.

### Reducing permissions and replacing a login

`$refresh(scopes = ...)` can reduce permissions while preserving required scopes.
Later refreshes and reauthorization keep that reduction. `required_scopes` on
`$access_token()` specifies the permissions needed for that operation. A provider can reject
narrowing; [Microsoft targets](token-targets.html#advanced-microsoft-permissions-and-consent)
do not support explicit API narrowing.

For OIDC, include `offline_access` when explicitly narrowing if later
reauthorization should still request refresh consent. Automatic refresh does
not remove that request just because the response omits it.

Standalone SMART replacements keep requested patient and encounter context.
Narrowing can remove optional encounter context, while retained patient
permissions still require patient context. See [SMART on FHIR](smart-on-fhir.html).

## Advanced: asynchronous token acquisition

Use `async = TRUE` when refreshing a token should not block the app. Set up
workers first, as shown in [Usage](usage.html#asynchronous-execution):

```{r, eval = FALSE}
# Configure mirai daemons or a non-sequential future plan before starting Shiny.
# Inside the owning Shiny session:
connection <- req(auth$connection())
pending <- connection$access_token(required_scopes = "records.read", async = TRUE)
promises::then(pending, function(token) {
  # Recheck before starting work after an asynchronous boundary.
  current <- req(auth$connection())
  req(identical(current$id, connection$id))
  req(current$has_scopes("records.read"))
  existing_sdk_read_records(access_token = token)
})
```

The callback compares the current connection and permissions because logout
or a permission change can occur while token acquisition runs. The other package
still needs its own background-request mechanism if its calls block R.

### Handling token errors

Token-acquisition errors inherit `shinyOAuth_access_error`. Inspect
`error$context$reason` when you want to offer a specific recovery action:

| Reason | Suggested response |
| --- | --- |
| `authorization_unavailable` | Obtain a current connection after login. |
| `insufficient_scope` | Explain which permission is missing. |
| `refresh_pending` | Await async work or let the user retry later. |
| `refresh_unavailable` | Try later or offer a new login. |
| `interaction_required` | Offer a new login. |
| `lifetime_unavailable` | The replacement token is too short-lived for the operation. |
| `unsupported_token_binding` | Use `$request()` with the configured DPoP or mTLS transport. |

Argument and configuration errors are separate. Token methods never open a
login page or repeat an SDK or database operation after an error.

## Advanced: background tasks and saved results

Acquire the token in the owning session before starting background work. Pass
plain inputs and that short-lived token to the worker, keeping the connection
and refresh credential in the session. This example uses Shiny's `ExtendedTask`
(Shiny 1.8.1 or newer):

```{r, eval=FALSE}
# Configure future::plan(future::multisession) before starting the app.
# Inside server(), with auth already created:
load_records <- shiny::ExtendedTask$new(function(token, authorization_id) {
  promises::future_promise({
    list(
      authorization_id = authorization_id,
      data = existing_sdk_read_records(access_token = token)
    )
  })
})

shiny::observeEvent(input$load, {
  shiny::req(load_records$status() != "running")
  connection <- shiny::req(auth$connection())
  token <- connection$access_token("records.read", min_valid_for_seconds = 120)
  load_records$invoke(token, connection$id)
})

output$records <- shiny::renderTable({
  connection <- shiny::req(auth$connection())
  shiny::req(connection$has_scopes("records.read"))
  result <- load_records$result()
  shiny::req(identical(result$authorization_id, connection$id))
  result$data
})
```

The requested token lifetime covers worker startup and the operation.
Before displaying results, the output checks connection ID and permissions;
the default `req()` behavior clears output when those checks fail.

See the [Shiny background-task guide](https://shiny.posit.co/r/articles/improve/nonblocking/)
for scheduling, and [OAuthConnection](https://lukakoning.github.io/shinyOAuth/reference/OAuthConnection.html) for connection lifetime and permission-reduction details.
