r-lib / gargle

Infrastructure for calling Google APIs from R, including auth
https://gargle.r-lib.org/
Other
113 stars 33 forks source link

Extend support of application default credentials #78

Open jennybc opened 5 years ago

jennybc commented 5 years ago

I've not given much attention to application default credentials for this first release. I've used it successfully now (with a service account token), but I've got a few lingering questions and ideas for future refinement/extended support.

Also, parking notes and links here.


Google Cloud Application Default Credentials (ADC) are not credentials. ADC is a strategy to locate Google Cloud Service Account credentials. If the environment variable GOOGLE_APPLICATION_CREDENTIALS is set, ADC will use the filename that the variable points to for service account credentials.

from https://www.jhanley.com/google-cloud-application-default-credentials

That does not fully capture all the locations checked by gargle::credentials_app_default(), but that is the first place it checks.

Official ADC docs: https://cloud.google.com/docs/authentication/production and https://cloud.google.com/sdk/docs/

I put in some minimal docs for gargle::credentials_app_default() via 4256749d345da60ecd96e0c442c2c472fc3064cb.

credentials_app_default() looks for a file at a path encapsulated by credentials_app_default_path(). Here's where it looks, in order, where ALL_CAPS indicates env var:

GOOGLE_APPLICATION_CREDENTIALS
CLOUDSDK_CONFIG/application_default_credentials.json
(APPDATA %||% SystemDrive %||% C:)/gcloud/application_default_credentials.json (Windows)
~/.config/gcloud/application_default_credentials.json (not Windows)

If a file exists at the path returned by credentials_app_default_path(), we parse it as JSON.

It is assumed that it (via info$type) declares itself to be an OAuth or service account token.

If it's OAuth, there's a bit of fiddling with scopes, then a new httr::Token2.0 is instantiated "by hand".

Question: should I just leave credentials_app_default() as is? Possible tweaks:

If this is a service account token, we call credentials_service_account() and then we're done.

jennybc commented 5 years ago

From test fixtures in googleapis/google-auth-library-nodejs (among a gazillion other places), here's what the ADC JSON should look like for user creds:

{
  "client_id": "client123",
  "client_secret": "clientSecret123",
  "refresh_token": "refreshToken123",
  "type": "authorized_user"
}

This is (sort of) documented here but @craigcitro tells me this is deprecated:

https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login

MarkEdmondson1234 commented 5 years ago

I came across this recently with respect to authentication on Jupyter notebooks. An example here.

Its only needed in the context of when you need user authentication e.g. Google Analytics vs general services like BigQuery where the default gce_credentials() are sufficient. I think its basically because the latter tokens are only scoped to /cloud, and you can't use the GCE auth for other scopes.

Question: how does one even end up with an OAuth2 token stored as this sort of JSON? I'm thinking maybe via the gcloud cli?

I was recommended to use the gcloud auth application login command to generate a token - but perhaps this is going soon if deprecated? https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login

gcloud auth application-default login \
    --client-id-file jupyter_client_id.json \
    --scopes=https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/analytics

## access the URL, login and create a verification code, paste in console.

## view then copy-paste the access token, to be passed into the R function
gcloud auth application-default print-access-token

I then copy paste that token given into a httr Token create function:

gar_gce_auth_default <- function(access_token, 
                                 scopes){

  json_creds <- jsonlite::fromJSON('~/.config/gcloud/application_default_credentials.json')

    httr::Token2.0$new(app = httr::oauth_app("google", 
                                 key = json_creds$client_id, 
                                 secret = json_creds$client_secret),
                       endpoint = httr::oauth_endpoints("google"),
                       credentials = list(access_token = access_token,
                                          token_type = json_creds$type,
                                          expires_in = NULL,
                                          refresh_token = NULL),
                       params = list(scope = scopes, type = NULL,
                                     use_oob = FALSE, as_header = TRUE),
                       cache_path = FALSE)

}

The whole thing could be smoother, and I wonder if its necessary if you can generate the access token via other means, as its seems gcloud auth application-default login is exactly the same as an OOB flow done via normal httr.

brokenjacobs commented 2 years ago

On osx I haven't been able to make gargle work with application default credentials. Python and golang libraries work but for whatever reason I get back a null from gargle.

camraynor commented 1 year ago

I ran into the same issue as @brokenjacobs on mac. It turned out that I was using a authorized_user account and I had not specified the scope correctly/it was not one of the four valid_scopes allowed by credentials_app_default()