Most major mail providers have moved to OAuth2.0 as their preferred (and, in many cases, only) authentication method for IMAP. Instead of sending your password, you send a short-lived access token that authorizes a specific scope of access to your mailbox.
This vignette shows the exact steps and R code to obtain, use, and refresh an OAuth2.0 access token for the Gmail IMAP server. The same logic applies to other providers (Outlook/Office 365, Yahoo, AOL, …) by swapping the authorization endpoints and scope. The reference for Google’s flow is https://developers.google.com/identity/protocols/oauth2.
We use the httr2
package to run the OAuth2.0 flow and jsonlite
to read the credentials file, so install them first if needed:
IMPORTANT — libcurl version. The libcurl feature
that transmits the bearer token is only reliable on libcurl >= 7.65.0
(released 2019-05-22). Check the version the curl R package
is linked against:
## [1] "8.18.0"
If it is older, update libcurl (and reinstall curl)
before continuing, otherwise you may get a SASL error
during authentication.
The whole process is:
configure_imap(xoauth2_bearer = ...);Steps 1–3 are done once, in the browser; steps 4–6 are R code.
mRpostman), and
click Create.You do not need to enable the Gmail API: IMAP access
is granted by the OAuth scope https://mail.google.com/, not
by the Gmail API. IMAP is enabled by default on Gmail accounts.
Navigate to APIs & Services > OAuth consent screen (in the redesigned console this section is called Google Auth Platform).
User type: choose External and click Create.
Fill in the required fields: App name, User support email, and, at the bottom, a Developer contact email. Save and continue.
Scopes: click Add or remove scopes, then, in the “manually add scopes” box, paste:
https://mail.google.com/Click Add to table, then Update and
Save and continue. (This is a restricted
scope; while your app stays in Testing mode it works
for test users without Google verification.) 4. Test
users: add the e-mail address of the account you will log in
with (e.g. [email protected]) and save.
The account you authenticate with in Step 4 must be
listed here as a test user, otherwise Google returns an
access_denied error.
redirect_uri_mismatch in the flow below).The downloaded file looks like this (an "installed"
client):
{"installed":{"client_id":"XXXX.apps.googleusercontent.com",
"project_id":"your-project","auth_uri":"https://accounts.google.com/o/oauth2/auth",
"token_uri":"https://oauth2.googleapis.com/token",
"client_secret":"GOCSPX-XXXX","redirect_uris":["http://localhost"]}}Read the client id and secret from it in R:
Below are two ways to get a token. Option A is the shortest and works out of the box on most desktops. Use Option B if Option A fails to start its local web server (common inside RStudio, or on headless/remote machines).
Both request access_type = "offline" so that a
refresh token is also returned (see Step 6).
httr2’s built-in flow (recommended)httr2::oauth_flow_auth_code() opens your browser, runs a
local callback server, and returns the token. (httr2 is the
successor of httr, which is in maintenance mode; the old
httr::oauth2.0_token() flow keeps working if you prefer
it.)
library(httr2)
client <- oauth_client(
id = cred$client_id,
secret = cred$client_secret,
token_url = "https://oauth2.googleapis.com/token",
name = "mRpostman")
tok <- oauth_flow_auth_code(
client,
auth_url = "https://accounts.google.com/o/oauth2/auth",
scope = "https://mail.google.com/",
auth_params = list(access_type = "offline", prompt = "consent"))
token <- tok$access_tokenWhen the browser shows “Google hasn’t verified this app”, click Advanced > Go to <your app> (unsafe) — this is expected while the app is in Testing mode — then Allow.
This avoids the local callback server entirely: you open the
authorization URL, approve access, and paste back the code
shown in the browser’s address bar.
library(httr2)
redirect_uri <- "http://localhost"
scope <- "https://mail.google.com/"
auth_url <- url_modify("https://accounts.google.com/o/oauth2/auth",
query = list(client_id = cred$client_id,
redirect_uri = redirect_uri,
response_type = "code",
scope = scope,
access_type = "offline",
prompt = "consent"))
browseURL(auth_url)After you approve, the browser is redirected to
http://localhost/?code=...&scope=.... The page will
fail to load (“unable to connect”) — that is fine. Copy the value
between code= and &scope from the
address bar and paste it below (authorization codes are
single-use and expire in minutes, so exchange it right away):
code_in <- "PASTE_THE_CODE_HERE"
resp <- request("https://oauth2.googleapis.com/token") |>
req_body_form(code = code_in,
client_id = cred$client_id,
client_secret = cred$client_secret,
redirect_uri = redirect_uri,
grant_type = "authorization_code") |>
req_perform()
token_data <- resp_body_json(resp)
token <- token_data$access_tokenYour token should be a long string starting with
"ya29.". If token is NULL,
inspect content(resp) — an invalid_grant error
means the code was already used or expired, so re-run
browseURL(auth_url) to get a fresh one. (On an
httr2 error, read the body with
resp_body_json(last_response()).)
Pass the access token to configure_imap() via the
xoauth2_bearer argument (no password):
library(mRpostman)
con <- configure_imap(
url = "imaps://imap.gmail.com",
username = "[email protected]",
use_ssl = TRUE,
xoauth2_bearer = token
)
con$list_server_capabilities()If this returns the server’s capabilities, you are authenticated and
can use any mRpostman method (select_folder(),
search_*(), fetch_*(), …).
Access tokens are short-lived (about 1 hour). The refresh
token returned in Step 4 (thanks to
access_type = "offline") lets you mint a new access token
without going through the browser again.
If you used Option A, mint a new access token from the refresh token:
If you used Option B, exchange the stored refresh token directly:
refresh <- request("https://oauth2.googleapis.com/token") |>
req_body_form(client_id = cred$client_id,
client_secret = cred$client_secret,
grant_type = "refresh_token",
refresh_token = token_data$refresh_token) |>
req_perform()
token <- resp_body_json(refresh)$access_tokenThen reopen (or update) the connection with the new
token. To swap the token on an existing connection object,
use con$reset_xoauth2_bearer(token).
access_denied / “hasn’t been verified by
Google”: the account you logged in with is not in the consent
screen’s Test users list (Step 2.4), or it is not the
account that owns the project. Add it as a test user.redirect_uri_mismatch: your OAuth
client is a Web application, not a Desktop app.
Recreate it as a Desktop app (Step 3).createTcpServer: address already in use (Option
A): the local callback port is busy — often a leftover server
from a previous attempt in the same R session. Run
httpuv::stopAllServers() and retry; if that fails, fully
quit and reopen RStudio (a mere “Restart R” does not release the port),
or switch to Option B, which needs no local
server.SASL error during authentication: your
libcurl is likely older than 7.65.0. Update it and reinstall the
curl R package (see the Introduction).oauth_endpoints("google") already fills them in), and set
url accordingly,
e.g. imaps://outlook.office365.com for Office 365.