r-lib/gargle

Out-of-bound flow is being deprecated

jennybc opened this issue · 23 comments

https://developers.googleblog.com/2022/02/making-oauth-flows-safer.html?m=1#disallowed-oob

OAuth out-of-band (OOB) is a legacy flow developed to support native clients which do not have a redirect URI like web apps to accept the credentials after a user approves an OAuth consent request. The OOB flow poses a remote phishing risk and clients must migrate to an alternative method to protect against this vulnerability. New clients will be unable to use this flow starting on Feb 28, 2022.

Key dates for compliance

  • Feb 28, 2022 - new OAuth usage will be blocked for the OOB flow
  • Sep 5, 2022 - a user-facing warning message may be displayed to non-compliant OAuth requests
  • Oct 3, 2022 - the OOB flow is deprecated for existing clients

There are two ways I recommend mitigating this deprecation:

  1. Default to using a web server on localhost for receiving tokens in places where the OOB flow was previously used. If you don't already have a means of finding an open port, I recommend doing so. You may want to reference this PR that handles this deprecation in one of the Python auth libraries: googleapis/google-auth-library-python-oauthlib#186
  2. To support folks running code on "headless" (no GUI to launch a browser) remote workstation, also add support to get credentials from the gcloud CLI if you don't already. The gcloud CLI has a special two-machine flow that allows folks to authenticate via gcloud auth application-default login --no-browser. You may wish to reference the Python code for reading these credentials. https://github.com/googleapis/google-auth-library-python/blob/dc040f5d8646fc362aee3716f3dd4a9d3355815d/google/auth/_default.py#L172

Thanks @tswast for your willingness to advise.

We do already have method 1 (use a local web server). It is a heavily-used auth flow in gargle and, more generally, in R API-wrapping packages, due to the support in the low-level httr and httr2 R packages. My main point is that we have experience with this and have, I think, long taken maximum advantage of it.

We do not support method 2, so that is something we will research and, presumably, implement.

However there’s another large use case for us: where the user is running R on a remote server via the browser. Examples: rstudio.cloud and RStudio Server Open Source / RStudio Workbench.

This is basically the situation that Google solves for itself on, e.g. GCE, by offering a special metadata service for token acquisition (which gargle also supports BTW).

Our hosted users are operating under various constraints that make "install and wrap the glcoud cli" challenging. RStudio Cloud users can't install arbitrary CLIs and there will be similar difficulties for many orgs running RStudio Server / Workbench.

So it’s not clear to us how to handle these very important use cases when OOB auth is removed.

Another rather unsavory option is for us to make it even easier to use method 1 on one computer (where it's possible to start a local web server) then upload those refreshable creds to another computer (where it's not). But that feels like a step backwards on many fronts.

For remote servers, especially ones with a web component, the recommended approach is to have a token endpoint for use by a redirect. Customers would have to create their own client IDs, though. This is how Tableau Server handles this, for example: https://help.tableau.com/current/server/en-us/config_oauth_google.htm

upload those refreshable creds to another computer (where it's not).

I agree that this option is rather unsavory, for all the same reasons we discourage the use of service account key files where possible. That said, it certainly is doable. I did end up implementing a CLI tool that does this in the Python ecosystem for folks that know what they're doing: https://github.com/pydata/pydata-google-auth/blob/7bc885d35ac8c9693631a33b3f4bdb5576d41d0e/pydata_google_auth/__main__.py#L75

Various measures are underway to prepare ourselves for the complete shutdown of Google OOB auth (cc @atheriel, @jcheng5). I have plans for some changes in gargle to better support and document the "move a token" approach and there will be some changes elsewhere to generally provide better OAuth support in our products.

Even though we're working on it, I also went ahead and requested an extension for the 4 OAuth clients I manage as part of the gargle project, to hopefully create a slightly longer ramp for these measures to be developed and settle into place and for users to learn of them.

Specifically, as per the email I received, I did this:

You may request a one-time extension for migrating your app until January 31, 2023. Keep in mind that all OOB authorization requests will be blocked on February 1, 2023.

If my request is granted, this moves the hard-fail date from October 3, 2022 to January 31, 2023.

Our request for extension has been granted, so we have a longer on-ramp for our various mitigation efforts to land.


Hello Google Developer,

We're writing to inform you that your request for an enforcement extension for the OAuth out-of-band (OOB) deprecation has been granted for the following OAuth client ID(s):

<redacted>

This extension:

  • only applies to the enforcement of deprecating support for OOB redirect URIs in requests to Google's OAuth 2.0 authorization endpoint
  • has only been granted for requests containing the OAuth clients using OOB flow listed in this email
  • expires January 31, 2023

The compliance for OOB flow deprecation will be enforced on February 1, 2023 with no exceptions or extensions.

What do I need to do?

Please review our Google Developers blog post, paying special attention to the OOB flow deprecation section, and make any necessary updates to your app(s).

Sincerely,

Google Developers Team

Comment from the peanut gallery: would it be possible to redirect to a generic landing page that can parse and present the authorization response outside remote RStudio environments? This may not be in the spirit of Google's OOB flow deprecation, but the approach used in the adobeanalyticsr package seems viable as a bridge solution.

This following steps would would have to be done for the baked-in gargle GCP as well as any individual users/projects where they want to use their own API credentials, but specifically:

  1. Create a simple page at a potential redirect address that can parse the authentication code out of the Google Auth response and present it to the user. (e.g. https://adobeanalyticsr.com/token_result.html)
  2. Configure the relevant GCP project to redirect users to that simple landing page rather than a localhost address. (I would hope this would be acceptable as it could be a single-page, "dumb" app served over Github pages or another secure host.)
  3. Instruct users to copy/paste the authorization code back into the RStudio Cloud, Workbench, etc. prompt.

It's certainly more fiddly, but would that work as a one-time setup/fix on a per-GCP project basis?

@mattpolicastro That's exactly the solution the gcloud client came up with as well. I believe to redirect to even a static HTML page like that you might need an alternative Client ID of the type "web application" in order to do it.

To avoid phishing, I'd highly recommend branding the page with your application and telling folks only to copy and paste the token if they initiated the request from an R notebook. Also, warn the user that the token has similar privileges to a password.

We are indeed exploring options along these lines, e.g. the experiment in #213.

Another option: OOB is not being deprecated for an External Client ID in Testing mode (pre-verification). This will require users create an OAuth 2.0 Credentials themselves in their own project and supply their own Client ID and Secrets to Gargle.

Thanks for both of your most recent comments @tswast!

We definitely have various adaptations in the works: a pseudo-oob flow, better docs around "bring your own token", and highlighting that conventional OOB still works for non-production GCP clients. It may look quiet here, but this will all roll out soon.

@tswast Can you point me to any more information about what the gcloud client is doing? I'm referring to this comment:

That's exactly the solution the gcloud client came up with as well.

Can you point me to any more information about what the gcloud client is doing? I'm referring to this comment:

If folks login to gcloud with the --no-launch-browser flag (documented here: https://cloud.google.com/sdk/gcloud/reference/auth/login#--launch-browser), it uses a web redirect to https://sdk.cloud.google.com/applicationdefaultauthcode.html which displays a token to copy-paste, along with a warning that it should be treated with similar care to a password. That is to say, basically the exact same flow described in #202 (comment)

@jennybc Huge thanks for creating the pseudo-oob flow here! I'm trying to leverage that in my package which uses its own oauth app/client. Currently the app is an "installed" one, so it will be impacted by the OOB deprecation. I want to use the pseudo-oob flow on a new "web" oauth client, but don't know what to supply as the redirect uri. It seems that tidyverse clients redirect to the static page (https://www.tidyverse.org/google-callback) hosted on tidyverse.org. Does it mean that all packages that want to leverage the gargle pseudo-oob flow need to set up a static website? What's the requirement of the site/page? I saw a inst/google-callback/index.html file in gargle, but not sure how I can use it.

Good question! I think there is nothing stopping you from using our page, but that would need to be empirically verified (or researched in docs, but I tried and failed). We haven't completely formed an official stance on that (other people using our static page), but since we haven't done so, I would not fault you for at least experimenting with this.

But yes all you / your package needs to do is to serve a page like that somewhere, such as within your package's pkgdown site. The file inst/google-callback/index.html is what you need. That is functionally the same as ours, but ours has tidyverse styling and branding (which is why I would prefer that you make your own page).

Is there somewhere documentation how to migrate to your own OOB web HTML? I have found the file indicated above but how to configure the console around it would be appreicated I guess not just for googleAnalyticsR.

Am I right in thinking it should only affect the personal user login screens, so the majority of my other GCP packages should be unaffected as they only use service keys? I guess then I will only need to also migrate searchConsoleR and possibly googleCloudStorageR.

I got this far but now a bit stuck:

  1. Created redirect URLs copying over from gargle that are live - https://code.markedmondson.me/googleAnalyticsR/dev/oob and https://code.markedmondson.me/googleAnalyticsR/oob
  2. Made new Web GCP OAuth client with those redirect URLs
  3. Made a gargle client
  4. Passed client into token_fetch()
googleAuthR::gar_oauth_app()
<gargle_oauth_client>
name: g-analytics-r_f987b853aac8324c70669bdec0b6f75e
id: 289759286325-h15qtntnnounclih9tnfdj8ft1dlh1n1.apps.googleusercontent.com
secret: <REDACTED>
type: web
redirect_uris: https://code.markedmondson.me/googleAnalyticsR/dev/oob,
https://code.markedmondson.me/googleAnalyticsR/oob

# this calls token_fetch() eventually with above client
> ga_auth()
# it did not open a web browser
 Error: Could not authenticate via any gargle cred function

Try telling your oauth client about only 1 of the redirect URIs, not both.

Also make sure use_oob = TRUE. I don't see that explicitly above and don't know enough about ga_auth() to know if/how it figures that out for itself.

I also suggest that you also take inspiration from this, in terms of the URL for your oob exchanger html (e.g. more like the tidyverse one https://www.tidyverse.org/google-callback/)

# inspired by these guidelines re: URIs associated with URL shorteners:
# 'redirect URI must either contain "/google-callback/" in its path or end
# with "/google-callback"'

gargle/R/Gargle-class.R

Lines 293 to 330 in 4b467a4

select_pseudo_oob_value <- function(redirect_uris) {
# https://developers.google.com/identity/protocols/oauth2/resources/oob-migration#inspect-your-application-code
bad_values <- c(
"urn:ietf:wg:oauth:2.0:oob",
"urn:ietf:wg:oauth:2.0:oob:auto",
"oob"
)
redirect_uris <- setdiff(redirect_uris, bad_values)
# https://developers.google.com/identity/protocols/oauth2/web-server#uri-validation
bad_regex <- "^http[s]?://(localhost|127.0.0.1)"
redirect_uris <- grep(bad_regex, redirect_uris, value = TRUE, invert = TRUE)
redirect_uris <- grep("^https", redirect_uris, value = TRUE)
# inspired by these guidelines re: URIs associated with URL shorteners:
# 'redirect URI must either contain "/google-callback/" in its path or end
# with "/google-callback"'
m <- grep("/google-callback(/|$)", redirect_uris)
if (length(m) > 0) {
redirect_uris <- redirect_uris[m]
}
if (length(redirect_uris) == 0) {
gargle_abort('
OAuth client (a.k.a "app") does not have a redirect URI suitable for \\
the pseudo-OOB flow.')
}
if (length(redirect_uris) > 1) {
msg <- c(
"Can't determine which redirect URI to use for the pseudo-OOB flow:",
set_names(redirect_uris, ~ rep_along(., "*"))
)
gargle_abort(msg)
}
redirect_uris
}

It was having two redirect URLs, edited my client JSON to only have one and got a successful OOB auth flow. Thanks!

We can even tolerate multiple URLs, as long as only one of them looks like a google callback URL. My hope is to work with wild-caught JSON as much as possible.

Good question! I think there is nothing stopping you from using our page, but that would need to be empirically verified (or researched in docs, but I tried and failed). We haven't completely formed an official stance on that (other people using our static page), but since we haven't done so, I would not fault you for at least experimenting with this.

But yes all you / your package needs to do is to serve a page like that somewhere, such as within your package's pkgdown site. The file inst/google-callback/index.html is what you need. That is functionally the same as ours, but ours has tidyverse styling and branding (which is why I would prefer that you make your own page).

Just to circle back on this. We deployed a static page internally and use that as the redirect uri for the oauth client. The pseudo-oob flow now works which is a huge relief as we're cutting very close to the Jan-2023 expiry date of the exception granted to use conventional OOB. Thanks again for the amazing work to make the pseudo-oob work!