We want to use OAUTH2 to authenticate an IMAP transaction, so that
no additional passwords are needed. To this we need a script to
manage the OAUTH2 flow.
Weirdly for an operation that’s becoming so common on the web,
there doesn’t seem to be a package that offers OAUTH2 from the
command line. However, there is a script that does it that’s
included as an example with the mutt
MUA, and we can use that. It
can be found (in Arch) in the mutt
package.
sudo pacman -S mutt
cp /usr/share/doc/mutt/samples/mutt_oauth2.py .
This puts a copy of the script into the current directory, which we
can then edit in two ways:
- add the internal application identification and client secrets
for accessing Office365; and
- set up the security for the OAUTH2 access tokens when they’re
downloaded and held locally.
The client secret and app id need to be “proper”, in the sense that
Office365 knows about them – but weirdly they don’t have to be
related to your email domain or cloud tenancy. It’s perfectly fine
to use credentials available in the public domain, for example
those of Thunderbird:
AppID = "08162f7c-0fd2-4200-a84a-f25a4db0b584"
ClientSecret = "TxRBilcHdC6WGBee]fs?QR:SJ8nI[g82"
(I got these from here, but other than that have no idea where
they come from: they’re not the same as those in the
Thunderbird source code, as far as I can tell.)
The mutt_oauth2.py
script stores the tokens it manages in a
gpg
-encrypted file. You therefore need to provide your gpg
keypair identification, and I’m assuming anyone wanting to get
local email has one of those! Mine is “simoninireland”.
GPGKey = "simoninireland"
I edited the file to look like this, with some details elided:
MSAppID = "08162f7c-0fd2-4200-a84a-f25a4db0b584"
MSClientSecret = "TxRBilcHdC6WGBee]fs?QR:SJ8nI[g82"
GPGKey = "simoninireland"
ENCRYPTION_PIPE = ['gpg', '--encrypt', '--recipient', GPGKey]
DECRYPTION_PIPE = ['gpg', '--decrypt']
registrations = {
'google': {
...
'client_id': '',
'client_secret': '',
},
'microsoft': {
...
'client_id': MSAppID,
'client_secret': MSClientSecret,
},
}
Put resulting script into /usr/local/bin
and make it executable.
Then run it in “authorisation” mode. The token file can go
anywhere: I put it in the directory used by pass
to allow for an
alternative access route:
mutt_oauth2.py -t .password-store/email/work.gpg --authorize
This will ask some questions:
- we want “microsoft” authentication
- and a “localhostauthcode” flow
- enter your email address (the actual user, not any alias)
and it prints out a URL to copy into a browser to authenticate
against Office365’s web interface. In my case this involved
interacting with the university’s single sign-on and two-factor
authentication (2FA) system. Doing this successfully put the
necessary OAUTH2 tokens, encrypted, into the specified file. Running:
mutt_oauth2.py -t .password-store/email/work.gpg
will output the token, refreshing it automatically if it’s
expired. This may ask for the GPG key’s passphrase, if it has one,
and if it’s not available from a local key agent.
(All this security means that the bearer tokens are stored
encryoted at rest. It’s a little inconvenient, though, as it means
you need to enter a gpg
passphrase periodically, and makes it
hard to run mbsync
in a cron
job. This is fine if, like me,
your level of security paranoia is such that you accept the minor
inconvenience in exchange for not having plain-text access tokens
lying around; on the other hand, you may decide that using, for
example, a machine with full-disc encryption is secure enough, in
which case you need to edit the ENCRYPTION_PIPE
and
DECRYPTION_PIPE
commands in the script to not do encryption: they
can basically just use cat
to store and retrieve the token information.)