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 httr package to run the OAuth2.0 flow and jsonlite to read the credentials file, so install them first if needed:
install.packages(c("httr", "jsonlite"))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:
curl::curl_version()$version## [1] "7.81.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.)
Test users: add the e-mail address of the account you will log in with (e.g. your_user@gmail.com) 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:
cred <- jsonlite::fromJSON("path/to/client_secret_XXXX.json")$installedBelow 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).
httr’s built-in flow (recommended)httr::oauth2.0_token() opens your browser, runs a local callback server, and caches the token in a .httr-oauth file.
library(httr)
gmail_app <- oauth_app("mRpostman",
key = cred$client_id,
secret = cred$client_secret)
token_obj <- oauth2.0_token(
endpoint = oauth_endpoints("google"),
app = gmail_app,
scope = "https://mail.google.com/",
cache = TRUE
)
token <- token_obj$credentials$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(httr)
redirect_uri <- "http://localhost"
scope <- "https://mail.google.com/"
auth_url <- modify_url("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 <- POST("https://oauth2.googleapis.com/token", encode = "form",
body = list(code = code_in,
client_id = cred$client_id,
client_secret = cred$client_secret,
redirect_uri = redirect_uri,
grant_type = "authorization_code"))
token_data <- content(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.
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 = "your_user@gmail.com",
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, httr refreshes automatically; you can also force it:
token_obj$refresh()
token <- token_obj$credentials$access_tokenIf you used Option B, exchange the stored refresh token directly:
refresh <- POST("https://oauth2.googleapis.com/token", encode = "form",
body = list(client_id = cred$client_id,
client_secret = cred$client_secret,
grant_type = "refresh_token",
refresh_token = token_data$refresh_token))
token <- content(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.