Accessing emails via XOAuth2

Marek Kadek
7 min readAug 29, 2020

--

Prelude
We will be looking into ways of accessing your Gmail via Google API, following it up further with a low-level approach — programmatically via SMTP & XOAuth2. This is a rather uncommon way, as the provided client APIs are sufficient in most cases. Next, we’ll approach cases that are not achievable with Gmail library clients.

This blog has educational purposes and is supplemented by code that demonstrates the principles outlined here. All of the code can be found in this repository.

Before proceeding, we need to get our app’s credentials (one that will perform the authorized actions on your behalf).

Getting app credentials
The easiest way to get the credentials of an app is to follow this section of official docs. Once you have gone through the steps from the link above, download the credentials file and put it to your resources directory.

Accessing Gmail via OAuth+API
The most common way to access Gmail is via the provided client that can be found here. To use the client we’ll start by creating an SBT project and adding it as a dependency.

libraryDependencies ++= Seq("com.google.apis" % "google-api-services-gmail" % "v1-rev110-1.25.0",  "com.google.api-client" % "google-api-client" % "1.23.0")

We need a way to obtain the OAuth token. We can request it at the start of our app by triggering authorization flow in the web browser. Upon completion, our app will be called by the browser to receive the token. Luckily for us, Google once again provides a library to help us deal with this. Let’s add it as a dependency to our build.sbt file:

libraryDependencies += "com.google.oauth-client" % "google-oauth-client-jetty" % "1.23.0"

Getting OAuth Access Token
Let’s start with code to request a user to authorize our app.

Notice the scope — GmailScopes.MAIL_GOOGLE_COM. This is a very broad permission scope. In general, it is a good practice to use a minimal scope (it’s also important if you want to get your app approved by Google for production). In this case, though, we need broad permission for SMTP access.

If we now call Setup.getToken() a web browser should open prompting us to approve permission. Upon approval, you should receive the access token.

Sign in window
Sign in to start the flow

Accessing Gmail API
Let’s try using the provided client to retrieve email address associated with the access token we received:

We receive back email addresses that we will use later with XOAuth. We could use the same API to list labels, emails, threads, and such — just as you would expect.

Accessing Gmail via SMTP+XOAuth
We need to include javax.mail dependency for working directly with emails:

libraryDependencies +=   "javax.mail" % "mail" % "1.4.7"

Let’s start by creating OAuth2SaslClient and OAuth2SaslClientFactory. We’ll need them to acquire proper Session and Transport for our emails. We are dealing with java interfaces, so the code is not idiomatic Scala, but it does the job. We’ll omit error handling as it’s not interesting for our problem.

Now we are ready to obtain Transport and Session instances, required for the successful sending of emails. We’ll do it in a class called OAuth2Authenticator.

Notice two error-prone parts:

  1. put(“SaslClientFactory.XOAUTH2”, “google.OAuth2SaslClientFactory”) — we reference the factory we prepared by a fully classified string name.
  2. Security.addProvider(new OAuth2Provider) — we need to ensure it’s been properly initialized inside providers

We’ll modify our getToken method to not only return access token but also associated email addresses. We’ll prepare a token that encodes XOAuth mechanism according to spec.

To populate it with values, we’ll use our old code:

Sending simple email
Let’s start by sending a simple email to the recipient. Even though it is easier to do the same using Gmail API for this as we don’t need to use SMTP or require such broad permission scopes, we’ll do it via SMTP as our first example. Recipients are defined in the Setup object and if you’re following with code along, make sure to update them to your email addresses.

Notice that we use the base64 encoded email-token pair:

transport.issueCommand(s"AUTH XOAUTH2 ${token.encoded}", 235)

The expected protocol response code on successful authentication is 235. Running this code, you should end up with an email in the recipient mailbox.

Received email
Email created by code, inside my Gmail mailbox

We can spot any errors that happen in the terminal because we have enabled debug mode during SMTP connection. In my setup, it looks like this:

MAIL FROM:<kadek.marek@gmail.com>
250 2.1.0 OK i4sm26997210wrw.26 - gsmtp
RCPT TO:<admin@byte2bite.com>
250 2.1.5 OK i4sm26997210wrw.26 - gsmtp

Nothing too surprising. RCPT TO is part of the SMTP envelope (along with MAIL FROM). It doesn’t have to match the emails TO header. In the next section, we’ll provide multiple RCPT TO to have blind copies forwarded.

Sending an email to the recipient with a blind copy
Let’s use RCPT TO header to send email to the recipient, while sending a blind copy to another recipient, without mentioning it in the email headers.

We have used transport.sendMessage method’s second argument to provide multiple recipients, other than the ones in the email header. We can see that the email was sent to two addresses:

MAIL FROM:<kadek.marek@gmail.com>
250 2.1.0 OK f124sm26159879wmf.7 - gsmtp
RCPT TO:<admin@byte2bite.com>
250 2.1.5 OK f124sm26159879wmf.7 - gsmtp
RCPT TO:<anton@byte2bite.com>
250 2.1.5 OK f124sm26159879wmf.7 - gsmtp

We can find the email inside Anton’s mailbox. This is not too surprising — we did send it to him after all. We only omitted him in the “to” part of headers. It would look similar if we forwarded the email — except then the subject usually gets prefixed by the client with “FW”.

Anton’s blind copy
Email from me, to Admin, inside Anton’s Gmail

Tracking opening of email by recipients
Some clients allow embedding tracking pixels that allow you to track whether the recipient has opened the email. A tracking pixel is an invisible image that is usually loaded with email (it’s as part of email content).

To load the image, your server is contacted by the browser when loading an email. For instance, a browser making a GET request to <img src=”https://my-tracking.com/id/666”> would yield information that id 666 was opened.

Not only is this approach not very reliable (sometimes email clients may preload images, but also nowadays it’s common for browsers not to load images without explicit permission), but on top of that, it’s arguably unethical.

This approach fails if we’re sending an email to a group of people, and we want to find out exactly who opened the email. We can get around this obstacle by sending a custom email to each recipient in a group with a new tracking pixel, while still leaving the appropriate email headers for the browser (so that it seems it’s a group email, instead of many ones to one emails).

Let’s give it a try by sending a “group” email to recipients, where (in reality) it will be two emails with different contents (that could otherwise be tracking pixels).

Notice that we sent two emails, but both with headers containing both recipients:

msg1.setHeader("To", s"${Setup.Recipient1}, ${Setup.Recipient2}")
msg1.setText("Some text for recipient1")
transport.sendMessage(msg1,
List(new InternetAddress(Setup.Recipient1)).toArray);
msg2.setHeader("To", s"${Setup.Recipient1}, ${Setup.Recipient2}")
msg2.setText("Some text for recipient2")
transport.sendMessage(msg2,
List(new InternetAddress(Setup.Recipient2)).toArray);

The important thing is that the body of each recipient differs — but they are not aware of it, due to each one receiving the same “to” headers. That leads to readers thinking that the other people mentioned in the email thread have received the same email.

Here is how recipients see it:

Email inside Admin’s mailbox
An email from me that is seemingly sent to both Admin and Anton, that is inside Admin’s mailbox
Email inside Anton’s mailbox
An email from me that is seemingly sent to both Admin and Anton, that is inside Anton’s mailbox

We can see the different email text. Normally, the content would be HTML, where only invisible parts of the email (such as the tracking pixel) would differ for each recipient, thus giving the real illusion that it’s a regular group email.

While this approach is far from perfect (at least as far as tracking and threading go), it would not be achievable via Java google client. It serves as a simple demonstration of what one can come up with when dealing with such a low-level protocol as SMTP. A similar approach could be used to connect to IMAP or POP3, but since we’re interested in cases that are not possible directly via the client, we’ll use SMTP to send emails.

Conclusion
We successfully received an access token for our app. We were able to call Google Gmail API using the acquired token. We connected via SMTP and sent emails. We looked at some examples that are not achievable via Java client.

References
https://developers.google.com/gmail/api/quickstart/java
https://developers.google.com/gmail/imap/xoauth2-protocol

--

--