The Gmail API is primarily intended for use on behalf of a regular Google user account, as opposed to a service account. The gmailr package guides an interactive R user through a process in which they authenticate themselves to Google and authorize Gmail activities initiated from R. This is sometimes referred to as the “OAuth dance”.
But what about settings where there is no interactive user sitting around, i.e. when gmailr-using code is deployed to a remote server or otherwise runs unattended? For most Google APIs, the standard advice is “use a service account”. But the Gmail API is special. To use a service account with the Gmail API basically requires that the service account has been delegated domain-wide authority. This is tricky for at least two reasons. First, this is only possible within a Google Workspace. It’s not available to personal Google accounts. Second, most Google Workspace admins will refuse to do this, for security reasons.
Therefore, if you want to deploy a data product that uses gmailr, it’s entirely possible that you really do need to use a user token. This article is about how to prepare a token for use in this scenario.
The instructions below involve filepaths and environment variables. Therefore, you will need to modify the code below to account for the specifics of your situation.
gmailr ships with code for a working demo of the approach described here.
We will make reference to these files below.
You may also wish to browse these files on GitHub: https://github.com/r-lib/gmailr/tree/main/inst/deployed-token-demo.
This process is recorded in the demo file
First, complete the OAuth dance in your primary, interactive
environment as the target user, using the desired OAuth client and
cache = FALSE. If you have arranged for the
desired OAuth client to be discovered via
gm_default_oauth_client(), you only need to call
If you need to specify the OAuth client explicitly, call
gm_auth_configure() prior to
You may wish to confirm that you are logged in as the intended user:
Now, write the current gmailr token to file. If you are deploying to
somewhere relatively private, such as a server accessible only within
your organization, you don’t need to provide any arguments to
gm_token_write(). But you’ll often want to specify the
The resulting token file is rather opaque, i.e. a general purpose automated tool can’t easily scrape your credentials from this. But a knowledgeable R programmer could decode the token, if they made an effort.
If the token file will be exposed in a more public location, such as
on GitHub or inside a CRAN package, it should be encrypted. You can
generate an encryption key with
(this is a copy of
httr2::secret_make_key(), which you
could also use). In your local development environment, make this key
available as an environment variable, e.g.
probably with a line like this in your
usethis::edit_r_environ() function can be handy for
creating and/or editing this file.
Once you’ve set up the encryption key, you can use it in
You must make this same
key available as a secret or
secure environment variable in the deployed context, e.g. on Posit
or GitHub Actions (https://docs.github.com/en/actions/security-guides/encrypted-secrets).
The demo file
an example of a working Shiny app (Shiny document, in this case) that
implements the technique described here..
In the code that’s running in the deployed / unattended setting, use a snippet like this to read the token from file and tell gmailr to use it:
If you did not specify the
gm_token_write(), omit it from the
gm_token_read() call as well. If you did specify the
gm_token_write(), use the same
The saved credential contains a refresh token, which is potentially rather long-lived, but is still perishable. As long as the refresh token remains valid, it can be used to obtain short-lived access tokens, without any user interaction. This is sometimes referred to as “refreshing the token” and this is what’s happening behind the scenes with a deployed token.
However, there are many ways that the refresh token can become invalid, for example:
The general topic of refresh token expiration is documented in https://developers.google.com/identity/protocols/oauth2#expiration.
If the token becomes invalid, token refresh will fail and your
deployed product will no longer be able to access the Gmail API on
behalf of the target user. It is a very good idea to rig your code to
surface this failure in a very transparent way, so it’s easier for you
to diagnose this problem. Functions like
gargle::token_userinfo() can be useful for this. If the
stored token can no longer be refreshed, the only remedy is to obtain,
store, possibly encrypt, and deploy a new token, using the exact same
process as before.