Extend support of application default credentials
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: how does one even end up with an OAuth2 token stored as this sort of JSON? I'm thinking maybe via the
gcloudcli? - If I knew exactly how to do this, gargle could offer a way to write existing user tokens this way, as a means of moving a project over to a server. For people who resist service account tokens and insist on user tokens, this would be a nice accommodation.
Question: should I just leave credentials_app_default() as is? Possible tweaks:
- Use
init_oauth2.0 ()instead ofhttr::Token2.0$new()? This seems to be a preferred workflow and yet I don't think it's possible, because we want to shove an existing refresh token in there. - Is the new
gargle::Gargle2.0subclass ofhttr::Token2.0relevant?
If this is a service account token, we call credentials_service_account() and then we're done.
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
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.
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.
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()