-
Notifications
You must be signed in to change notification settings - Fork 56
/
deploy-a-token.Rmd
152 lines (113 loc) · 6.7 KB
/
deploy-a-token.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
---
title: "Deploy a token"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Deploy a token}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
eval = FALSE
)
```
```{r setup, message = FALSE, eval = TRUE}
library(gmailr)
```
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.
## Demo code
gmailr ships with code for a working demo of the approach described here.
```{r eval = TRUE}
writeLines(list.files(
system.file("deployed-token-demo", package = "gmailr")
))
```
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>.
## Setup: store a token
*This process is recorded in the demo file `token-setup.R`.*
First, complete the OAuth dance in your primary, interactive environment as the target user, using the desired OAuth client and scopes, with `cache = FALSE`.
If you have arranged for the desired OAuth client to be discovered via `gm_default_oauth_client()`, you only need to call `gm_auth()`:
```{r}
gm_auth("jane@example.com", cache = FALSE)
```
If you need to specify the OAuth client explicitly, call `gm_auth_configure()` prior to `gm_auth()`:
```{r}
gm_auth_configure("path/to/your/oauth_client.json")
gm_auth("jane@example.com", cache = FALSE)
```
You may wish to confirm that you are logged in as the intended user:
```{r}
gm_profile()
```
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 target `path`:
```{r}
gm_token_write(path = "path/to/gmailr-token.rds")
```
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 `gargle::secret_make_key()` (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. `GMAILR_KEY`, probably with a line like this in your `.Renviron` file:
```
GMAILR_KEY=xxxxxxxxxxxxxxx
```
The `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 `gm_token_write(key =)`:
```{r}
gm_token_write(
path = "path/to/gmailr-token.rds",
key = "GMAILR_KEY"
)
```
You must make this same `key` available as a secret or secure environment variable in the deployed context, e.g. on Posit Connect (https://docs.posit.co/connect/admin/security/#application-environment-variables) or GitHub Actions (https://docs.github.com/en/actions/security-guides/encrypted-secrets).
## Usage: load and use token
*The demo file `send-email-byo-encrypted-token.Rmd` is 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:
```{r}
gm_auth(token = gm_token_read(
path = "path/to/gmailr-token.rds",
key = "GMAILR_KEY"
))
```
If you did not specify the `key` in `gm_token_write()`, omit it from the `gm_token_read()` call as well.
If you did specify the `key` in `gm_token_write()`, use the same `key` in `gm_token_read()`.
## Ongoing maintenance
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:
* In the Security settings for their Google user account, a user can remove
access associated with a specific OAuth client or app. This invalidates any
token obtained with that client.
* If a token isn't used for a period of time (~6 months), it becomes invalid.
* If an OAuth client is deleted or its host project disables the Gmail API, any
associated tokens become invalid.
* There's a limit to how many refresh tokens a user can have. If a user
repeatedly mints new tokens (versus refreshing existing ones), older tokens
will "fall off the end" and become invalid.
* If an OAuth client is in "testing" mode, all associated tokens have a limited
lifetime, usually 1 week.
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 `gm_profile()` and `gm_has_token()` 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.