October 21, 2010

Gmail IMAP with OAuth

Gmail provides IMAP OAuth interface. With this API, developer can access authorized user’s mailbox without password. While OAuth mechanism is complicated but its procedure is quite simple, even you don’t have to register your app to Google.

Authorization procedure can be breaked down into five steps:

  1. Get request token from Google
  2. Redirect user to Google account auth page with the request token
  3. After user allows the app, user is redirected back to our site with verifier code.
  4. Request access token to Google with the verifier code. Then authorization is completed.
  5. You can access the user’s mailbox by using standard IMAP protocol with the access token. Access token never expires, except when the user revoked access to the app.

Followings are example code by Perl, but what we send and receive are same to other languages.

#!/usr/bin/perl
use strict;
use warnings;
use Net::OAuth;
use LWP::UserAgent;
use HTTP::Request::Common;
use URI;

$Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A;

my $ua = LWP::UserAgent->new;
my $request = Net::OAuth->request('request token')->new(
    consumer_key     => 'anonymous',
    consumer_secret  => 'anonymous',
    request_url      => 'https://www.google.com/accounts/OAuthGetRequestToken',
    request_method   => 'GET',
    signature_method => 'HMAC-SHA1',
    timestamp        => time,
    nonce            => int( rand(2**64-1) ), # random string
    callback         => 'http://yourserver.example.com/callback', # change to your URL
    extra_params     => {
        scope => 'https://mail.google.com/',
    },
);
$request->sign;

# Get request token
my $res = $ua->request( GET $request->to_url );
my $uri = URI->new;
# Response is like this:
# oauth_token=xxxx&oauth_token_secret=xxxx&oauth_callback_confirmed=true
# Split into key and value
$uri->query( $res->decoded_content );
my $tokens = $uri->query_form_hash;
my $request_token = $tokens->{oauth_token};
my $request_token_secret = $tokens->{oauth_token_secret};

# Redirect user to this URL
my $redirect_url = 'https://www.google.com/accounts/OAuthAuthorizeToken?oauth_token='.$tokens->{oauth_token}."¥n";

# After user granted access, the user will be redirected to the callback URL with
# oauth_verifier and oauth_token in query string:
# http://yourserver.example.com/callback?oauth_verifier=xxxx&oauth_token=xxxx

# Get oauth_verifier and oauth_token from URL in some way
my $verifier = 'xxxx';

# Then request the access token with the verifier
$request = Net::OAuth->request('access token')->new(
    consumer_key     => 'anonymous',
    consumer_secret  => 'anonymous',
    request_url      => 'https://www.google.com/accounts/OAuthGetAccessToken',
    request_method   => 'GET',
    signature_method => 'HMAC-SHA1',
    timestamp        => time,
    nonce            => int( rand(2**64-1) ), # random string
    token            => $request_token,
    token_secret     => $request_token_secret,
    verifier         => $verifier,
);
$request->sign;

$res = $ua->request( GET $request->to_url );
$uri = URI->new;
# response is like this:
# oauth_token=xxxx&oauth_token_secret=xxxx
# Split into key and value
$uri->query( $res->decoded_content );
my $access_tokens = $uri->query_form_hash;

# This is what you need
my $access_token = $access_tokens->{oauth_token};
my $access_token_secret = $access_tokens->{oauth_token_secret};

Now you can access the mailbox in this way:

#!/usr/bin/perl
use strict;
use warnings;
use Mail::IMAPClient;
use Net::OAuth;
use MIME::Base64;

# Gmail address
my $email = 'example@gmail.com';
# Access tokens that you got before
my $access_token = 'xxxx';
my $access_token_secret = 'xxxx';

my $url = 'https://mail.google.com/mail/b/'.$email.'/imap/';

my $request = Net::OAuth->request('access token')->new(
    consumer_key     => 'anonymous',
    consumer_secret  => 'anonymous',
    request_url      => $url,
    request_method   => 'GET',
    signature_method => 'HMAC-SHA1',
    timestamp        => time,
    nonce            => int( rand( 2 ** 64 - 1 ) ), # random string
    token            => $access_token,
    token_secret     => $access_token_secret,
);
$request->sign;

my $auth_header = $request->to_authorization_header;
$auth_header =‾ s/^OAuth¥s*//;
my $xoauth_string = "GET $url $auth_header";
my $xoauth_base64 = encode_base64($xoauth_string, '');

my $imap = Mail::IMAPClient->new(
    Server   => 'imap.gmail.com',
    User     => $email,
    Port     => 993,
    Ssl      => 1,
    Uid      => 1,
    Debug    => 1,
);
die "Can't connect to IMAP server: $@" unless $imap;

$imap->authenticate('XOAUTH', sub { $xoauth_base64 } )
    or die "Could not authenticate: " . $imap->LastError;

$imap->select('INBOX');
Posted by Nao Iizuka <iizuka@kyu-mu.net>
Powered by Bitter