IMAP OAuth2.0 authentication in mRpostman

Introduction

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:

  1. create a Google Cloud project;
  2. configure the OAuth consent screen (scope + test user);
  3. create a Desktop app OAuth client and download its JSON credentials;
  4. obtain an access token in R;
  5. open the IMAP connection with configure_imap(xoauth2_bearer = ...);
  6. refresh the token when it expires.

Steps 1–3 are done once, in the browser; steps 4–6 are R code.

Step 1 - Create a Google Cloud project

  1. Go to https://console.cloud.google.com/ and sign in with the Google account whose mailbox you want to access.
  2. In the project selector at the top, click New Project, give it a name (e.g. mRpostman), and click Create.
  3. Make sure this new project is the one selected in the top bar.

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.

Step 3 - Create the OAuth client credentials

  1. Go to APIs & Services > Credentials.
  2. Click + Create credentials > OAuth client ID.
  3. For Application type, select Desktop app (this is important — a Web application client will fail with redirect_uri_mismatch in the flow below).
  4. Give it a name and click Create.
  5. Click Download JSON and save the file locally. Keep it private — it contains your client secret.

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")$installed

Step 4 - Obtain an access token

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).

Option B - Manual loopback flow (no local server)

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_token

Your 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.

Step 5 - Open the IMAP connection

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_*(), …).

Step 6 - Refresh the token

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_token

If 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_token

Then reopen (or update) the connection with the new token. To swap the token on an existing connection object, use con$reset_xoauth2_bearer(token).

Troubleshooting