# Transition from OAuth app to OAuth client

    library(gargle)

Over the course of several releases (v1.3.0, v1.4.0, and v1.5.0), gargle has shifted to using an OAuth client in the user flow facilitated by `gargle::credentials_user_oauth2()`, instead the previous OAuth “app”. This is a more than just a vocabulary change (but it is also a vocabulary change). This vignette explains what actually changed and how wrapper packages should adjust.

## Why change was needed

In 2022, Google partially deprecated the out-of-band (OOB) OAuth flow. The OOB flow is used by R users who are working with Google APIs and who use R in the browser, such as via RStudio Server, Posit Workbench, Posit Cloud, or Google Colaboratory.

Conventional OOB auth still works under certain conditions, for example, if the OAuth client is associated with a GCP project that is in testing mode or that is internal to a Google Workspace. But conventional OOB is no longer supported for projects that serve external users that are in production mode. In particular, this means that conventional OOB is no longer supported for the GCP project that has historically made auth “just work” for casual users of packages such as googledrive, googlesheets4, and bigrquery. The default OAuth client used by these package no longer works with conventional OOB.

In response, as of v1.3.0, gargle implements a new variant of OOB, called pseudo-OOB, to continue to provide a user-friendly auth flow for googledrive/googlesheets4/bigrquery on RStudio Server/Posit Workbench/Posit Cloud/Google Colaboratory. The pseudo-OOB flow is also available for other developers to use. This flow is triggered when `use_oob = TRUE` (an existing convention in gargle and gargle-using packages) and the configured OAuth client is of the web type (when creating an OAuth client, this is called the “Web application” type).

|  | use_oob
--- | --- | ---
|  | FALSE | TRUE
client type | installed | use httpuv to spin up a temporary web server | conventional OOB
web | --not possible-- | pseudo-OOB

In the past, gargle basically assumed that every OAuth client was of the installed type (when creating an OAuth client, this is called the “Desktop app” type). Therefore, the introduction of pseudo-OOB meant that gargle had to learn about different OAuth client types (web vs. installed). And that didn’t play well with `httr::oauth_app()`, which gargle had been using to store the client ID and secret.

That’s why there is a new S3 class, `"gargle_oauth_client"`, with a constructor of the same name. Since more information is now necessary to instantiate a client (e.g. its type and, potentially, redirect URIs), the recommended way to create a client is to provide JSON downloaded from the GCP console to `gargle_oauth_client_from_json()`.

Since we had to introduce a new S3 class and supporting functions, we also took this chance to make the vocabulary pivot from “OAuth app” to “OAuth client”. Google’s documentation has always talked about the “OAuth client”, so this is more natural. This vocabulary is also more future-facing, anticipating the day when gargle might shift from httr to httr2, which uses `httr2:oauth_client()`. As a bridging measure, the `"gargle_oauth_client"` class currently inherits from httr’s `"oauth_app"`, but this probably won’t be true in the long-term.

### How to instantiate an OAuth client in R

If you do auth via gargle, here are some recommended changes:

1. Stop using `httr::oauth_app()` or `gargle::oauth_app_from_json()` to instantiate an OAuth client.
2. Start using `gargle_oauth_client_from_json()` (strongly recommended) or `gargle_oauth_client()` instead.

This advice applies to anything you do inside your package and also to what you encourage and document for your users.

gargle ships with JSON files for two non-functional OAuth clients, just to make this all more concrete:

    (path_to_installed_client <- system.file(
      "extdata", "client_secret_installed.googleusercontent.com.json",
      package = "gargle"
    ))
    #> [1] "/private/tmp/RtmpfBi3gI/Rinst89b755898995/gargle/extdata/client_secret_installed.googleusercontent.com.json"
    jsonlite::prettify(scan(path_to_installed_client, what = character()))
    #> {
    #>     "installed": {
    #>         "client_id": "abc.apps.googleusercontent.com",
    #>         "project_id": "a_project",
    #>         "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    #>         "token_uri": "https://accounts.google.com/o/oauth2/token",
    #>         "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    #>         "client_secret": "ssshh-i-am-a-secret",
    #>         "redirect_uris": [
    #>             "http://localhost"
    #>         ]
    #>     }
    #> }
    #>
    (client <- gargle_oauth_client_from_json(path_to_installed_client))
    #> <gargle_oauth_client>
    #> name: a_project_d1c5a8066d2cbe48e8d94514dd286163
    #> id: abc.apps.googleusercontent.com
    #> secret: <REDACTED>
    #> type: installed
    #> redirect_uris: http://localhost
    class(client)
    #> [1] "gargle_oauth_client" "oauth_app"

    (path_to_web_client <- system.file(
      "extdata", "client_secret_web.googleusercontent.com.json",
      package = "gargle"
    ))
    #> [1] "/private/tmp/RtmpfBi3gI/Rinst89b755898995/gargle/extdata/client_secret_web.googleusercontent.com.json"
    jsonlite::prettify(scan(path_to_web_client, what = character()))
    #> {
    #>     "web": {
    #>         "client_id": "abc.apps.googleusercontent.com",
    #>         "project_id": "a_project",
    #>         "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    #>         "token_uri": "https://accounts.google.com/o/oauth2/token",
    #>         "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    #>         "client_secret": "ssshh-i-am-a-secret",
    #>         "redirect_uris": [
    #>             "https://www.tidyverse.org/google-callback/"
    #>         ]
    #>     }
    #> }
    #>
    (client <- gargle_oauth_client_from_json(path_to_web_client))
    #> <gargle_oauth_client>
    #> name: a_project_d1c5a8066d2cbe48e8d94514dd286163
    #> 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"

Notice the difference in the JSON for the installed vs. web client. Note the class of the `client` object, the new `type` field, and the `redirect_uris`.

## `AuthState` class

There are two gargle classes that are impacted by the OAuth-app-to-client switch: `AuthState` and `Gargle2.0`. We cover `AuthState` here and `Gargle2.0` in the next section.

If a wrapper package follows the design laid out in `vignette("gargle-auth-in-client-package")`, it will use an instance of `AuthState` to manage the package’s auth state. Let’s assume that internal object is named `.auth`, which it usually is. Here are the changes you need to know about in `AuthState`:

- The `app` field is deprecated, in favor of a new field `client`. If you request `.auth$app`, there will be a deprecation message and the `client` field is returned.
- The `$set_app()` method is deprecated, in favor of a new `$set_client()` method. If you call `.auth$set_app()`, there will be a deprecation message and the input is used, instead, to set the `client` field.
- The `app` argument of the `init_AuthState()` constructor is deprecated in favor of the new `client` argument. If you call `init_AuthState(app = x)`, there will be a deprecation message and the input `x` is used as the `client` argument instead.

Here are the changes you probably need to make in your package:

- The first argument of the user-facing function, `PKG_auth_configure()`, should become `client` (which is new). Move the existing `app` argument to the last position and deprecate it.
- Deprecate `PKG_oauth_app()` (the function to reveal the user’s configured OAuth client).
- Introduce `PKG_oauth_client()` to replace `PKG_oauth_app()`.

Here’s how `googledrive::drive_auth_configure()` and `googledrive::drive_oauth_client()` looked before and after the transition:

    # BEFORE
    drive_auth_configure <- function(app, path, api_key) {
      # not showing this code
      .auth$set_app(app)
      # more code we're not showing
    }

    drive_oauth_app <- function() .auth$app

    # AFTER
    drive_auth_configure <- function(client, path, api_key, app = deprecated()) {
      if (lifecycle::is_present(app)) {
        lifecycle::deprecate_warn(
          "2.1.0",
          "drive_auth_configure(app)",
          "drive_auth_configure(client)"
        )
        drive_auth_configure(client = app, path = path, api_key = api_key)
      }

      # not showing this code
      .auth$set_client(client)
      # more code we're not showing
    }

    drive_oauth_client <- function() .auth$client

    drive_oauth_app <- function() {
      lifecycle::deprecate_warn(
        "2.1.0", "drive_oauth_app()", "drive_oauth_client()"
      )
      drive_oauth_client()
    }

The approach above follows various conventions explained in `vignette("communicate", package = "lifecycle")`. If you also choose to use the lifecycle package to assist in this process, `usethis::use_lifecycle()` function does some helpful one-time setup in your package:

    usethis::use_lifecycle()

The roxygen documentation helpers in gargle assume `PKG_auth_configure()` is adapted as shown above:

- `PREFIX_auth_configure_description()` crosslinks to `PREFIX_oauth_client()` now, not `PREFIX_oauth_app()`.
- `PREFIX_auth_configure_params()` documents the `client` argument
- `PREFIX_auth_configure_params()` uses a lifecycle badge and text to communicate that `app` is deprecated.
- `PREFIX_auth_configure_params()` crosslinks to `gargle::gargle_oauth_client_from_json()` which requires gargle (>= 1.3.0)

## `Gargle2.0` class

`Gargle2.0` is the second gargle class that is impacted by the OAuth-app-to-client switch.

Here are the changes you probably need to make in your package:

- Inside `PKG_auth()`, you presumably call `gargle::token_fetch()`. If you are passing `app = <SOMETHING>`, change that to `client = <SOMETHING>`. Neither `app` nor `client` are formal arguments of `gargle::token_fetch()`, instead, these are intended for eventual use by `gargle::credentials_user_oauth2()`. Here’s a sketch of how this looks in `googledrive::drive_auth()`:

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

- If you ever call `gargle::credentials_user_oauth2()` directly, use the new `client` argument instead of the deprecated `app` argument.