Configuring aerc

I finally migrated from mutt to aerc for my TUI-based email needs. This is how I set up authentication.

Gmail

There are two ways to authenticate to Gmail: using an application password and using OAuth. The former is not ideal because the access is scoped to the entire Google account rather than just reading and writing email. The latter is not ideal because it’s difficult to set up, but I did it that way so wanted to write it down somewhere.

Generate an OAuth Token

Visit console.cloud.google.com and create a new project with a sensible name (e.g. “aerc”) and unique project ID.

Then under “APIs and services”, we need to create a credential and OAuth consent screen, in reverse order. The consent screen is cosmetic, no need to add any scopes, just fill in the mandatory fields and include your own account as a test user. Once done, create an OAuth credential of type “Desktop”.

Generate a Refresh Token

Now we have an OAuth client ID and client secret. This authenticates the application, not an end user; we have to now authenticate an end user (ourself) to the application with the correct scope. One way to do this is to run auth-source-xoauth2’s google-oauth tool.

This command reads a secret from a command line argument, which is dangerous because it appears in the process tree and command line history. The latter can be avoided by prefixing it with a space in Zsh.
make
...
 ./oauth -client-id "{client_id}" -client-secret "{client_secret}"
Visit the URL for the auth dialog: https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&...
YYYY/MM/DD HH:MM:SS Got "/?state=state&code=...&scope=https://mail.google.com/"

AccessToken: ...
TokenType: Bearer
RefreshToken: ...
Expiry: ...

Build Account Configuration

We now have enough to construct aerc’s source URL. From aerc-imap(5),

source = <scheme>://<username>[:<password>]@<hostname>[:<port>]?[<oauth2_params>]

and since we’ll use imaps+oauthbearer as the scheme which has token_endpoint, client_id, client_secret and scope as optional parameters, the URL will look like

imaps+oauthbearer://{username}:{refresh_token}@imap.gmail.com?token_endpoint=https%3A%2F%2Foauth2.googleapis.com%2Ftoken&client_id={client_id}&client_secret={client_secret}

where each variable above is URL-encoded, e.g. username%40gmail.com for the username. This means the refresh_token almost certainly has to be URL-encoded too. No need to set the port, we use the default of 993.

The outgoing URL is almost the same (from aerc-smtp(5)),

smtps+oauthbearer://{username}:{refresh_token}@smtp.gmail.com:465?token_endpoint=https%3A%2F%2Foauth2.googleapis.com%2Ftoken&client_id={client_id}&client_secret={client_secret}

where only the scheme, hostname and port changed. Again, no need to set the port, we use the default of 465 (for SMTPS, not 587 for STARTTLS).

One drawback of this is that secrets end up in the configuration file. This can be partially mitigated by using source-cred-cmd and outgoing-cred-cmd which will replace the password component of the URL (for us, the refresh_token) with the standard out of the program we specify. For example,

source            = imaps+oauthbearer://{username}@imap.gmail.com?token_endpoint=https%3A%2F%2Foauth2.googleapis.com%2Ftoken&client_id={client_id}&client_secret={client_secret}
outgoing          = smtps+oauthbearer://{username}@smtp.gmail.com?token_endpoint=https%3A%2F%2Foauth2.googleapis.com%2Ftoken&client_id={client_id}&client_secret={client_secret}
source-cred-cmd   = pass mail/oauth/mail.google.com | head -n 1
outgoing-cred-cmd = pass mail/oauth/mail.google.com | head -n 1
default           = INBOX
folders-sort      = INBOX
postpone          = [Gmail]/Drafts
from              = {name} <{username}>
cache-headers     = true

where the refresh token is not URL-encoded, aerc will do that for us now. This still leaves the client secret, which there’s nothing we can do about.

maths.tcd.ie

This is much simpler. Use

source            = imaps://{username}@imap.maths.tcd.ie
outgoing          = smtp://{username}@smtp.maths.tcd.ie
source-cred-cmd   = pass mail/maths.tcd.ie | head -n 1
outgoing-cred-cmd = pass mail/maths.tcd.ie | head -n 1
from              = {name} <{username}@maths.tcd.ie>
default           = INBOX
copy-to           = Sent
cache-headers     = true

where {username} doesn’t include the domain (i.e. not like the Gmail configuration above).

References

There are a few other resources for this, such as Setting up Gmail on aerc, How to setup aerc with Gmail and OAuth2 and the aerc wiki. I found them helpful but I ended up deviating from them in different ways.