Skip to content

Instantly share code, notes, and snippets.

@mmas
Last active December 26, 2015 09:19
Show Gist options
  • Save mmas/7129077 to your computer and use it in GitHub Desktop.
Save mmas/7129077 to your computer and use it in GitHub Desktop.
Google OAuth2 with Tornado 3.1.1 (Python)
import json
import urllib
import httplib
import re
from tornado import httpclient
from tornado.web import asynchronous, RequestHandler
from tornado.auth import OAuth2Mixin
from tornado.gen import coroutine
from tornado.concurrent import return_future
class GoogleLogin(RequestHandler, OAuth2Mixin):
_OAUTH_AUTHORIZE_URL = "https://accounts.google.com/o/oauth2/auth"
_OAUTH_ACCESS_TOKEN_URL = "https://accounts.google.com/o/oauth2/token"
_OAUTH_TOKEN_VALIDATION_URL = "https://www.googleapis.com/oauth2/v1/tokeninfo"
_USER_INFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo"
GOOGLE_OAUTH_SCOPES = ('https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile')
GOOGLE_CLIENT_ID = 'XXX'
GOOGLE_CLIENT_SECRET = 'XXX'
def prepare(self, *args, **kwargs):
super(GoogleLogin, self).prepare(*args, **kwargs)
next_uri = self.get_cookie('next_uri')
if not next_uri:
if self.request.uri == self.reverse_url('login'):
# next_uri: uri to redirect after the login.
self.set_cookie('next_uri', self.reverse_url('home'))
else:
self.set_cookie('next_uri', self.reverse_url('home'))
self.redirect_uri = '%s://%s%s' % (self.request.protocol,
self.request.host,
self.reverse_url('login'))
@asynchronous
@coroutine
def get(self):
code = self.get_argument('code', None)
if code:
yield self.get_authenticated_user(code)
else:
yield self.authorize_redirect(
redirect_uri=self.redirect_uri,
client_id=self.GOOGLE_CLIENT_ID,
extra_params={'scope': ' '.join(self.GOOGLE_OAUTH_SCOPES),
'response_type': 'code'})
@return_future
def get_authenticated_user(self, code, callback=None):
args = {'redirect_uri': self.redirect_uri,
'client_id': self.GOOGLE_CLIENT_ID,
'code': code,
'client_secret': self.GOOGLE_CLIENT_SECRET,
'grant_type': 'authorization_code'}
request = httpclient.HTTPRequest(self._OAUTH_ACCESS_TOKEN_URL,
method='POST',
body=urllib.urlencode(args))
callback = self.async_callback(self._on_access_token)
self.auth_http_client.fetch(request, callback)
def _on_access_token(self, response):
token = json.loads(response.body)['access_token']
request = self._OAUTH_TOKEN_VALIDATION_URL + '?access_token=' + token
callback = self.async_callback(self._on_get_user_info, token)
self.auth_http_client.fetch(request, callback)
def _on_get_user_info(self, token, response):
if response.error:
self.set_status(500)
self.finish()
conn = httplib.HTTPSConnection('www.googleapis.com')
headers = {'Authorization': 'Bearer ' + token}
uri = '/oauth2/v1/userinfo?access_token=' + token
conn.request('GET', uri, '', headers)
try:
response = conn.getresponse()
response = json.loads(response.read())
except:
self.set_status(500)
self.finish()
# Timezone offset can be get by javascript as:
# new Date().getTimezoneOffset()
tz_offset = eval(self.get_cookie('timezone_offset', '0'))
user = {'id': response['id'],
'email': response['email'],
'name': response['name'],
'given_name': response['given_name'],
'family_name': response['family_name'],
'locale': self.parse_locale(response.get('locale')),
'picture': response.get('picture'),
'timezone_offset': tz_offset}
self.clear_cookie('timezone_offset')
self.set_secure_cookie('user', json.dumps(user))
next_uri = self.get_cookie('next_uri')
self.clear_cookie('next_uri')
self.redirect(next_uri)
@property
def auth_http_client(self):
return httpclient.AsyncHTTPClient()
def parse_locale(self, locale):
if locale:
match = re.match(r'(\w+)[-_](\w+)[.]?', locale)
if match:
try:
return '%s_%s' % (match.group(1).lower(),
match.group(2).upper())
except IndexError:
pass
return 'en_US'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment