I'm trying to access my Gmail emails over IMAP using XOAUTH2 in Ruby.
I've successfully generated an access token (and refresh token) by authenticating using OAuth 2.0 with the oauth2
gem. I'm going to use gmail_xoauth
to access Gmail over IMAP. So I now need to generate the SASL initial client response, as per the Gmail XOAuth2 docs:
The SASL XOAUTH2 initial client response has the following format:
base64("user=" {User} "^Aauth=Bearer " {Access Token} "^A^A")
using the base64 encoding mechanism defined in RFC 4648.
^A represents a Control+A (\001).
I'm not clear how I represent the "Control+A" in my string. Do I simply use ^A
?
key = Base64.encode64("user=#{email}^Aauth=Bearer #{access_token_obj.token}^A^A")
This python script uses \1
in place of ^A
. I've also tried \001
. Whatever I try, when authenticating (in irb) with the result I get:
>> imap = Net::IMAP.new('imap.gmail.com', 993, usessl=true, certs=nil, verify=false)
>> imap.authenticate('XOAUTH2', email, key)
OpenSSL::SSL::SSLError: SSL_write:: bad write retry
That error could be entirely unrelated, but I'm not confident any option I've tried is correct.
Finally figured it out... I didn't need to do the Base64 encoding step at all!
gmail_xoauth
adds the XOAUTH
authenticator to Net::IMAP
itself. I realised that this only expects the unencoded access_token
from Google, rather than the longer Base64-encoded string.
So, if:
email = `[email protected]`
# The result of the OAuth2 dance (as well as a refresh_token):
access_token = 'ya13.AHES6Y3F54_5fAoz_8VuG-7pzQAo3R0_ukt7dhfgRnJh41Q'
then I don't have to Base64 encode anything. I just do:
imap = Net::IMAP.new('imap.gmail.com', 993, usessl=true, certs=nil, verify=false)
imap.authenticate('XOAUTH2', email, access_token)
and I get back:
#<struct Net::IMAP::TaggedResponse tag="RUBY0001", name="OK", data=#<struct Net::IMAP::ResponseText code=nil, text="[email protected] Fred Bloggs authenticated (Success)">, raw_data="RUBY0001 OK [email protected] Fred Bloggs authenticated (Success)\r\n">
(As a bonus, this is a handy Ruby script for getting the access_token
from the OAuth dance.)