---
title: "Authentication flow"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Authentication flow}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

## Overview

shinyOAuth runs the OAuth 2.0 Authorization Code flow inside a Shiny app.
The browser visits the provider for login and consent, then returns with an
authorization code. shinyOAuth exchanges that code for tokens, validates the
response, and updates the module's reactive authentication state.

The provider object describes the service, the client object holds the app's
registration and policy, and `oauth_module_server()` manages each Shiny session.
The UI wrapper handles the HTTP callback and browser setup. With OpenID Connect
(OIDC), the flow also validates identity through a signed ID token. OAuth-only
providers such as GitHub and Spotify supply profile information through their
own APIs.

The [usage vignette](usage.html) contains the application setup. The sequence
below describes the work performed by the package after that setup.

## Starting login and binding it to the browser

With `auto_redirect = TRUE`, the module starts login for an unauthenticated
session. With `auto_redirect = FALSE`, `auth[["request_login"]]()` initiates the
same flow. Both paths wait for browser setup before preparing the request.

For each login transaction, the module establishes a fresh random browser
binding. The browser holds the binding token in origin- and tab-scoped session
storage, with a separate temporary cookie marker. On return, the
module checks this binding against the pending transaction. This connects the
callback to the browser tab that started it, rather than accepting state from
any browser presenting the same callback URL.

The binding is separate from provider-issued access and ID tokens. Browser
storage and cookie setup happen through the UI dependency; unavailable browser
facilities produce a browser-setup error before login proceeds.

## Preparing the authorization request

The authorization builder, also exposed through `prepare_call()` and
`prepare_authorization_request()`, combines the provider's authorization endpoint
with the client's ID, redirect URI, scopes and configured request parameters.
The module prepares the transaction values used during later validation:

| Value | How the package uses it |
| --- | --- |
| `state` | Carries authenticated, encrypted transaction data and links the callback to the pending login record. |
| PKCE verifier and challenge | The request carries the challenge; the later token exchange supplies the verifier, binding the exchange to this request. |
| OIDC nonce | The returned ID token is checked against the stored nonce when nonce validation is enabled. |

Generic OIDC helpers enable PKCE and nonce checks by default. Named provider
helpers select defaults appropriate to their service. S256 is the default PKCE
method when PKCE is enabled.

### Pending state and expiry

The client state store retains the browser binding, PKCE verifier, nonce and
transaction context needed for the return. The encrypted state and stored
context also record the client/provider policy used to start login. A callback
cannot continue under a changed validation policy.

`state_payload_max_age` limits the encrypted state's age, while the store's
retention determines how long the pending record remains available. Both
default to five minutes. State consumption is single-use, so replaying a
completed callback cannot exchange the code again.

The default store belongs to one R process. A shared store and `state_key` let
another process continue the same transaction, with atomic record consumption
providing the single-use guarantee across processes.

## Authorization at the provider

The module sends the browser to the prepared authorization URL, or submits a
browser form when the client selects POST authorization. The provider handles
credentials and consent. Existing provider sessions or earlier consent may
allow it to return without showing another prompt.

The authorization result contains a short-lived code or an error such as
`access_denied`, together with the transaction state. The provider sends this
result to the registered redirect URI. The code is exchanged by R; it is not
the access token used in later API calls.

## Receiving and validating the callback

`oauth_ui()` processes query callbacks before application HTML or scripts load.
It validates the callback address and response envelope, stores a sealed,
short-lived response, and redirects to a clean continuation URL carrying a
one-time handle. `oauth_form_post_ui()` provides the corresponding path for
POST callbacks. The wrappers also set no-store and no-referrer response headers.

After the HTTP continuation, the Shiny module checks the initiating browser
and consumes the pending transaction before code exchange.

### Transaction checks

The module checks that the state is authentic and within its lifetime, that
the stored client/provider policy still matches, and that the browser binding
agrees with the pending record. The callback route and any required issuer
identifier are checked against trusted client configuration. Missing, expired,
already-consumed or mismatched records reject the callback.

A provider error response goes through state and browser validation too.
Only a valid response for this transaction can become the session's login error.

### Several authorization servers

For a configured client registry, the HTTP wrapper selects the client by its
registered callback route or issuer. Distinct-route mode checks the callback
address. Issuer mode uses the RFC 9207 `iss` or the JARM response's issuer as
a routing hint; the selected client's issuer, signature, transaction and browser
checks establish trust.

## Exchanging the code for tokens

After callback validation, shinyOAuth sends the code to the token endpoint,
along with the PKCE verifier and the authentication required by the app's
registration. `token_auth_style` selects that authentication, such as a client
secret, signed assertion or client certificate.

The token response is still untrusted at this point. shinyOAuth parses it,
validates the access token and token type, calculates expiry, and reconciles
reported scopes with the requested permissions. `scope_validation` determines
whether a reduced grant produces a warning or rejects login. The token records
`granted_scopes` and whether the response explicitly verified that grant in
`granted_scopes_verified`.

For ordinary OAuth responses that omit `expires_in`, the package uses
`shinyOAuth.default_expires_in`, defaulting to 3,600 seconds. SMART clients apply
their separate lifetime policy.

Code exchange and refresh are not automatically retried after ordinary network
or HTTP failures, because the provider may already have consumed the code or
rotated a refresh credential. DPoP nonce challenges have their own one-retry
handling.

## Validating identity and profile information

### ID token checks

With OIDC validation enabled, shinyOAuth verifies the ID token using the
provider's public signing keys. Key retrieval and caching follow the provider's
configured issuer and JWKS policy. Decoding claims is followed by signature
and claim validation before the identity is accepted.

| Check | What it establishes |
| --- | --- |
| Signature and allowed algorithm | The token was signed with an acceptable provider key. |
| `iss` | The token came from the configured issuer. |
| `aud` and, when present, `azp` | The token is intended for this client; additional audiences require explicit trust. |
| `sub` | The token identifies a subject within that issuer. |
| `exp`, `iat` and optional `nbf` | The token is within its validity window. |
| Nonce, when enabled | The ID token belongs to this login request. |
| `auth_time`, when freshness was requested | The provider login satisfies the requested maximum age. |
| `at_hash`, when present or required | The ID token agrees with the returned access token. |

Time checks allow the configured clock leeway, defaulting to 30 seconds.
The package also applies its configured maximum ID-token lifetime, defaulting
to 24 hours. Requested claim requirements follow `claims_validation`, while
`required_acr_values` checks the validated authentication-context claim against
the client's accepted values.

### Introspection and UserInfo

With `introspect = TRUE`, shinyOAuth asks the provider whether the access token
is active. The response must report `active = TRUE` and satisfy the configured
`introspection_checks`. Introspection runs before profile retrieval, allowing
it to supply binding information for opaque certificate- or DPoP-bound tokens.
Checks involving the profile finish after that profile becomes available.

When `userinfo_required = TRUE`, the package fetches the provider's UserInfo
endpoint or profile API. A failed required fetch rejects login. For OIDC, when
both a validated ID token and UserInfo are available, their `sub` values must
match. The provider's subject-binding policy can also require a validated ID
token to be present for the profile fetch.

UserInfo can be JSON or a signed JWT. Signed responses go through signature and
claim validation, and `userinfo_signed_jwt_required` rejects ordinary JSON when
the registration requires signing. OAuth-only helpers obtain their profiles
through the service's API without creating an OIDC identity assertion.

## Publishing authentication state

After all configured checks pass, the module publishes the `OAuthToken` as
`auth[["token"]]` and sets `auth[["authenticated"]]` to `TRUE`. Reactive consumers
then see the accepted token and available profile. A rejected login instead
populates `auth[["error"]]` and `auth[["error_description"]]`.

The token holds the access and refresh credentials, expiry, granted scopes,
validated ID token claims and fetched profile. Additional response parameters
are available in `extra_fields`; `initial_extra_fields` preserves the initial
code-exchange values across refreshes.

The module's `connection()` method resolves the current credentials through an
`OAuthConnection`. Connection methods apply session ownership, current access
and configured permission checks when obtaining tokens or making requests.

## Asynchronous execution

With `async = TRUE` and mirai workers or a non-sequential future plan, the module
dispatches network work to background R processes while the main process
continues servicing Shiny. The module updates authentication state after the
worker result passes validation. It checks that a result belongs to the current
authorization, so work completing after logout or replacement cannot restore
the old login.

State-store access and hosted Request Object publication remain in the main
process. Signed-request preparation, PAR and query-JARM verification can run in
workers. Setup-time discovery and POST-wrapper JARM verification remain
synchronous. This describes the module's work; API calls made by application
code have their own execution context.

## Refresh, reauthentication and logout

### Token refresh

The module watches token expiry. With `refresh_proactively = TRUE` and an
available refresh token, it schedules refresh before expiry. Connection token
acquisition can also trigger refresh on demand. Overlapping asynchronous
refreshes coordinate use of the same credential.

A refresh response passes token validation, configured introspection and any
required profile fetch before replacing the current token. A rotated refresh
token replaces the previous one. If the provider omits a new ID token, the
stored identity token remains; a new ID token is checked for continuity with
the original subject, issuer and audience, plus nonce and authentication time
when applicable. Certificate-bound tokens require fresh binding data.

Ordinary refresh requests preserve the retained scope limit. SMART clients and
API-specific token targets apply their corresponding scope rules. Refresh
updates credentials without establishing a new interactive login.

### Expiry and maximum login age

By default, token expiry or refresh failure clears the local token and makes
`authenticated` false. An in-progress refresh has a grace period before expiry
clearing. `reauth_after_seconds` independently limits the age of the interactive
login; refresh does not restart that timer. With automatic
redirects enabled, the module starts authorization again when appropriate.

For OIDC age-based reauthentication, the request includes `max_age = 0` and the
returned `auth_time` is checked. OAuth-only providers receive an ordinary new
authorization request, which may reuse their existing login session.

`indefinite_session = TRUE` changes local clearing behavior: the module retains
login state after expiry or refresh failure, reports `token_stale`, and ignores
the maximum-login-age limit.
With token targets, individual API tokens can expire while the shared local
authorization remains; connection methods check the selected target before use.

### Logout and revocation

`auth[["logout"]]()` clears local credentials and resets the browser binding.
It also attempts token revocation when the provider supports it. With
`revoke_on_session_end = TRUE`, session termination and automatic clearing also
attempt revocation. These requests follow the module's sync or async mode;
remote failure does not prevent local cleanup.

Provider account sessions are separate, so local logout can be followed by
another authorization without a provider password prompt. Retained connections
have their own lifetime and ownership rules, described in
[Multiple authorizations](multiple-authorizations.html).

## Optional protocol extensions

Configured extensions add these steps to the sequence:

| Extension | Package behavior |
| --- | --- |
| JAR | Signs the authorization parameters as a Request Object before browser navigation. Optional encryption hides the contents. |
| PAR | Sends the request directly to the provider first, then redirects the browser with the returned reference. |
| Form Post | Receives the authorization result by POST and bridges it into the Shiny session. |
| JARM | Verifies the signed authorization response before processing its callback fields; configured encryption adds decryption first. |
| mTLS | Presents the client certificate and uses configured endpoint aliases; certificate-bound token checks apply when enabled. |
| DPoP | Signs proofs and handles nonce challenges for token requests and resource requests made through the configured connection or request helpers. |

[Advanced security](advanced-security.html) describes these configurations.
Detailed contracts are documented under
[oauth_client()](https://lukakoning.github.io/shinyOAuth/reference/oauth_client.html),
[oauth_provider()](https://lukakoning.github.io/shinyOAuth/reference/oauth_provider.html),
[handle_callback()](https://lukakoning.github.io/shinyOAuth/reference/handle_callback.html)
and [refresh_token()](https://lukakoning.github.io/shinyOAuth/reference/refresh_token.html).

## Audit trail

The flow emits events for request preparation, callback checks, token operations
and authentication-state changes. Related events share a `trace_id`, and failed
checks include operation or phase context. The configured audit hook receives
these events; OpenTelemetry can export the same events and operation timings.
The [audit logging](audit-logging.html) and [OpenTelemetry](opentelemetry.html)
vignettes describe those records.
