rgoogle-oauthgarglerstudio-connect

Encrypted token to drive_auth in deployed setting--still tries cache


I develop my code on posit workbench. I knit a Rmd file and deploy ("publish with source code") to rstudio-connect. The Rmd needs to authenticate my google account, and I use googledrive::drive_auth(). Since I don't want my auth token shared when I publish the source code, I encrypt the token following the steps outlined here to create an encrypted .rds file. When I want to authenticate, I decrypt the token and pass it to drive_auth, but drive_auth fails unless I include the original cached binary file in my deployed resources. Is there a way to prevent that behavior and use only the token that I have encrypted?

googledrive version 2.1.1 gargle version 1.5.2

First, to setup my auth, in an interactive session, I run the following code:

library(gargle)
library(googledrive)

Specify the gargle cache directory and other options. Cache directory must be in the same directory as the Rmd file (analysis) to publish to rstudio-connect.

dir.create(here::here("analysis/secrets"))
options(
  gargle_oauth_cache = here::here("analysis/secrets"),
  gargle_oauth_email = "xxx@xxx.com",
  gargle_oauth_client_type = "web"
        )

Do initial gdrive auth (interactive)

drive_auth() # this creates a binary file in the secrets directory

Use key to encrypt the drive oauth cached token. Key created with gargle::secret_make_key() and added to my .Renviron file. I also add it to rstudio-connect's {X} Vars, so it will be used as an environment variable. Place the encrypted token in the same cache directory.

gargle::secret_write_rds(
  drive_token(),
  here::here("analysis/secrets/gdrive-token.rds"),
  key = "GARGLE_ENCRYPT_KEY"
)

Then in a Rmd file (test.Rmd), I have:

library(gargle)
library(googledrive)

options(
  gargle_oauth_cache = "secrets",
  gargle_oauth_email = "xxx@xxx.com",
  gargle_oauth_client_type = "web",
  gargle_verbosity = "debug"
)

Code to check the directory when knitting and deployed.

getwd()
## [1] "opt/rstudio-connect/mnt/app"

list.files()
## [1] "manifest.json" "packrat"    "secrets"    "test.Rmd"

dir.exists(gargle::gargle_oauth_cache())
## [1] TRUE

Make sure the decrypted token is valid.

httr2::secret_has_key("GARGLE_ENCRYPT_KEY")
## [1] TRUE

gargle::secret_read_rds(
    "secrets/gdrive-token.rds",
    key = "GARGLE_ENCRYPT_KEY"
  )
## <request>
## Auth token: Gargle2.0
drive_auth(
  token = gargle::secret_read_rds(
    "secrets/gdrive-token.rds",
    key = "GARGLE_ENCRYPT_KEY"
  )
)
  1. When I knit the file, it works.
  2. When I deploy (or "publish with source code"), it fails when I include only secrets/gdrive-token.rds as the additional resource file. I get this generic error message:
Error in `drive_auth()`:
! Can't get Google credentials.
ℹ Are you running googledrive in a non-interactive session? Consider:
• Call `drive_deauth()` to prevent the attempt to get credentials.
• Call `drive_auth()` directly with all necessary specifics.
ℹ See gargle's "Non-interactive auth" vignette for more details:
ℹ <https://gargle.r-lib.org/articles/non-interactive-auth.html>
Backtrace:
 1. googledrive::drive_auth(...)
Execution halted
Unable to render the deployed content: Rendering exited abnormally: exit status 1
  1. But if I deploy and include the original cached token binary file secrets/<token binary>, the deployment succeeds. Looking at the messages, though, it seems like there might be an issue with the cache? Why does it try to use the cache when I give it the token? And when it does try the cache, why does it try the cache that was given at first in the interactive session? Here are the debugging messages I get when the deployment succeeds:
drive_auth(
  token = gargle::secret_read_rds(
    "secrets/gdrive-token.rds",
    key = "GARGLE_ENCRYPT_KEY"
  )
)
## trying `token_fetch()`
## Trying `credentials_byo_oauth()` ...
## ! The `scopes` cannot be specified when user brings their own OAuth token.
## ℹ The `scopes` are already implicit in the token.
## ℹ Requested `scopes` are effectively ignored.
## ! Token's declared scopes are not the same as the requested scopes.
## ℹ Scopes declared in token: ...drive, ...userinfo.email
## ℹ Requested scopes: ...drive
## putting token into the cache:
## '<directory of gargle cache on posit-workbench, not rstudio-connect>'
## Warning caught by `token_fetch()`:
## cannot open compressed file
## '<directory of gargle cache on posit-workbench, not rstudio-connect>/<token binary>',
## probable reason 'No such file or directory'
## Error caught by `token_fetch()`:
## cannot open the connection
## trying `credentials_service_account()`
## Error caught by `token_fetch()`:
## Argument 'txt' must be a JSON string, URL or file.
## trying `credentials_external_account()`
## aws.ec2metadata not installed; can't detect whether running on EC2 instance
## trying `credentials_app_default()`
## Trying `credentials_gce()` ...
## ✖ We don't seem to be on GCE.
## trying `credentials_user_oauth2()`
## attempt to access internal gargle data from: googledrive
## Gargle2.0 initialize
## adding "userinfo.email" scope
## loading token from the cache
## email: 'xxx@xxx.com'
## oauth client name: tidyverse-erato
## oauth client name: web
## oauth client id:
## <suppressed>
## scopes: ...drive, ...userinfo.email
## token(s) found in cache:
## <token binary>
## token we are looking for:
## <token binary>
## matching token found in the cache

Solution

  • I did some debugging and it turns out, the issue is caused by the cache_path written within the token itself (the one you create locally at the beginning) that is written as an absolute path (within your local filesystem).

    Then inside Rmd file the auth process seems to be as followed:

    1. Using cache:

      a) within the path gargle_oauth_cache = "secrets" - this fails without error (or pass when original token is there)
      b) when above failed, try cache_path saved within the token - this fails with error (invalid on connect only as it is absolute path from your local filesystem)

    2. Auth via the provided token.

    So we never hit the 2nd point. 1b. indeed works on local env but fails on connect env.

    You can easily solve the issue by turning off cache (that in fact we don't need to use at all):

    1. Set gargle_oauth_cache = FALSE in your token generation script (remember to regenerate the token and save it to rds).
    2. Remove gargle_oauth_cache = "secrets" in Rmd file.

    The two simple changes should do the job. It's even the saver approach, cause we don't save the original token in the filesystem at all (when cache is turned on, the token is saved within the cache directory anyway).