Transition from OAuth app to OAuth client

library(gargle)

In 2023, over the course of versions 1.3.0, 1.4.0, and 1.5.0, gargle switched from an OAuth “app” to an OAuth client in the user flow facilitated by credentials_user_oauth2(). gargle introduced client-based functions, arguments, and fields and deprecated the app-based ones, while keeping them working.

That transition is now wrapping up, in two stages:

  1. Wrapper packages remove their own deprecated app interfaces, such as the app argument of PKG_auth_configure() and the PKG_oauth_app() function, and stop using gargle’s deprecated app interfaces. The tidyverse and r-lib packages that wrap Google APIs are doing this now.
  2. A future version of gargle removes its own deprecated app arguments, methods, and functions.

This vignette is a checklist for maintainers of wrapper packages. A package that satisfies every item is ready for both stages.

Background

In 2022, Google partially deprecated the conventional out-of-band (OOB) OAuth flow, which is how many people authenticate when they use R in a browser, e.g., on Posit Workbench or Google Colaboratory. In response, gargle 1.3.0 introduced a new flow, pseudo-OOB, that needs to know the type of the OAuth client (“installed” or “web”) and its redirect URIs. httr::oauth_app(), which gargle had been using to store a client’s ID and secret, has no place for this information, so gargle introduced a new S3 class, "gargle_oauth_client". We also took the chance to adopt the term “OAuth client”, which is what Google’s documentation has always used (and what httr2 uses, in httr2::oauth_client()). For now, the "gargle_oauth_client" class also inherits from httr’s "oauth_app" class, but don’t rely on that.

See vignette("auth-from-web") for more about conventional OOB, pseudo-OOB, and client types.

Checklist for wrapper packages

The examples below come from googledrive, whose prefix is drive. They assume the design laid out in vignette("gargle-auth-in-client-package"), in which an internal AuthState object named .auth manages the package’s auth state.

Create OAuth clients with gargle

Create an OAuth client with gargle_oauth_client_from_json() (strongly recommended) or gargle_oauth_client(). Don’t use httr::oauth_app() or the deprecated oauth_app_from_json(). Likewise, use gargle_client(), not the deprecated gargle_app(), and, in a tidyverse package, use tidyverse_client(), not the deprecated tidyverse_app(). This applies to your package’s code and to what you show your users in examples and documentation.

The JSON downloaded from Google Cloud Console records the client’s type and redirect URIs, which is why it’s the preferred way to create a client. gargle ships with JSON for a non-functional client of each type. Here’s the “web” one:

path <- system.file(
  "extdata", "client_secret_web.googleusercontent.com.json",
  package = "gargle"
)
(client <- gargle_oauth_client_from_json(path))
#> <gargle_oauth_client>
#> name: a_project_59aa1797ab7ac4667a77617c3400999c
#> id: abc.apps.googleusercontent.com
#> secret: <REDACTED>
#> type: web
#> redirect_uris: https://www.tidyverse.org/google-callback/
class(client)
#> [1] "gargle_oauth_client" "oauth_app"

The “installed” one is client_secret_installed.googleusercontent.com.json, in the same folder.

Use the client interfaces of AuthState

Pass client to gargle’s token functions

Inside PKG_auth(), you presumably call token_fetch(). Pass the OAuth client as client, not app. Neither is a formal argument of token_fetch(); it’s passed along to credentials_user_oauth2(). Here’s a sketch of googledrive::drive_auth():

drive_auth <- function(...) {
  # code not shown
  cred <- gargle::token_fetch(
    scopes = scopes,
    client = drive_oauth_client() %||% gargle::tidyverse_client(),
    email = email,
    path = path,
    package = "googledrive",
    cache = cache,
    use_oob = use_oob,
    token = token
  )
  # code not shown
}

If you call credentials_user_oauth2() or gargle2.0_token() directly, use their client argument, not app.

Finish the user-facing functions

PKG_auth_configure() should take client as its first argument and should have no app argument. PKG_oauth_client() reveals the user’s configured client and PKG_oauth_app() should not exist.

If your package deprecated app and PKG_oauth_app() during the transition, it’s time to remove them, along with the lifecycle::is_present(app) branch in PKG_auth_configure(). Mention the removal in NEWS.md. Here’s how this looks in googledrive:

drive_auth_configure <- function(client, path, api_key) {
  # code not shown
  .auth$set_client(client)
  # code not shown
}

drive_oauth_client <- function() .auth$client

If your package still has app as the main argument of PKG_auth_configure(), you can’t remove it right away, because your users may be relying on it. Introduce client and PKG_oauth_client() and deprecate app and PKG_oauth_app() for at least one release, following the conventions in vignette("communicate", package = "lifecycle").

Re-document with current gargle

gargle has unexported roxygen helpers that generate standard documentation for auth functions, e.g., #' @eval gargle:::PREFIX_auth_configure_params(). In current gargle:

These helpers run when you re-document, so the gargle version installed at that moment is what matters. Re-document with current gargle after you remove app from PKG_auth_configure(). An older gargle documents app, which leads to an R CMD check warning about a documented argument that’s not in the function’s usage.

What’s next in gargle

Once wrapper packages have made these changes, a future version of gargle will remove its own deprecated app interfaces:

A package that satisfies the checklist above won’t be affected.