--- title: "IMAP OAuth2.0 authentication in mRpostman" output: rmarkdown::html_vignette: toc: yes vignette: > %\VignetteIndexEntry{IMAP OAuth2.0 authentication in mRpostman} %\VignetteEngine{knitr::rmarkdown} \usepackage[utf8]{inputenc} --- ## Introduction {#intro} 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 . We use the [`httr2`](https://cran.r-project.org/package=httr2) package to run the OAuth2.0 flow and [`jsonlite`](https://cran.r-project.org/package=jsonlite) to read the credentials file, so install them first if needed: ```{r, eval = FALSE} 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: ```{r, message = FALSE} curl::curl_version()$version ``` 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 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 2 - Configure the OAuth consent screen 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): ```json {"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: ```{r, eval = FALSE} 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 A - `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.) ```{r, eval = FALSE} 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 \ (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. ```{r, eval = FALSE} 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): ```{r, eval = FALSE} 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`): ```{r, eval = FALSE} 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: ```{r, eval = FALSE} 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: ```{r, eval = FALSE} 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](#intro)). - **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.