Skip to contents

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 httr2 package to run the OAuth2.0 flow and jsonlite to read the credentials file, so install them first if needed:

install.packages(c("httr2", "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] "8.14.1"

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.

Navigate to APIs & Services > OAuth consent screen (in the redesigned console this section is called Google Auth Platform).

  1. User type: choose External and click Create.

  2. Fill in the required fields: App name, User support email, and, at the bottom, a Developer contact email. Save and continue.

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

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

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_token

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

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(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_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. (On an httr2 error, read the body with resp_body_json(last_response()).)

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, mint a new access token from the refresh token:

tok   <- oauth_flow_refresh(client, refresh_token = tok$refresh_token)
token <- tok$access_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_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

  • 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.
  • Work or institutional accounts (Microsoft 365, Google Workspace): organizational tenants commonly block third-party OAuth apps by default, so the login fails until the IT administrator approves the app (or the client you registered) for the tenant. This is a property of the tenant’s policy, not of this package — REST clients such as gmailr and Microsoft365R are subject to the same approval. Personal accounts are not affected.
  • SASL error during authentication: your libcurl is likely older than 7.65.0. Update it and reinstall the curl R package (see the Introduction).
  • Other providers: replace the authorization/token endpoints and the scope with the provider’s values (for Google, oauth_endpoints("google") already fills them in), and set url accordingly, e.g. imaps://outlook.office365.com for Office 365.