Simple Webapp2 Google App Engine Example¶
In this tutorial we will create a simple Google App Engine Webapp2 application that will be able to log the user in with Facebook, Twitter and OpenID and retrieve the user’s 5 most recent tweets/statuses.
First create the Config dictionary:
# config.py
from authomatic.providers import oauth2, oauth1, openid, gaeopenid
CONFIG = {
'tw': { # Your internal provider name
# Provider class
'class_': oauth1.Twitter,
# Twitter is an AuthorizationProvider so we need to set several other properties too:
'consumer_key': '########################',
'consumer_secret': '########################',
},
'fb': {
'class_': oauth2.Facebook,
# Facebook is an AuthorizationProvider too.
'consumer_key': '########################',
'consumer_secret': '########################',
# But it is also an OAuth 2.0 provider and it needs scope.
'scope': ['user_about_me', 'email', 'publish_stream'],
},
'gae_oi': {
# OpenID based Google App Engine Users API works only on GAE
# and returns only the id and email of a user.
# Moreover, the id is not available in the development environment!
'class_': gaeopenid.GAEOpenID,
},
'oi': {
# OpenID based on python-openid library works everywhere,
# is flexible, but requires more resources.
'class_': openid.OpenID,
}
}
Create main.py file and import what’s needed. We don’t need much.
# main.py
import webapp2
from authomatic import Authomatic
from authomatic.adapters import Webapp2Adapter
from config import CONFIG
Make an instance of the Authomatic class and pass the Config together with a random secret string used for session and CSRF token generation to it’s constructor.
authomatic = Authomatic(config=CONFIG, secret='some random secret string')
Create a simple request handler which accepts GET and POST HTTP methods and receives the provider_name URL variable.
class Login(webapp2.RequestHandler):
def any(self, provider_name):
Log the user in by calling the Authomatic.login() method inside the handler. You must pass it an adapter for your framework, in our case the Webapp2Adapter and one of the provider names that you defined in the Config, which we get from the URL path of the request.
The method will redirect the user to the specified provider to prompt him/her for consent and redirect him/her back to this handler.
result = authomatic.login(Webapp2Adapter(self), provider_name)
The login procedure is over when Authomatic.login() returns a LoginResult.
Warning
Do not write anything to the response unless the login procedure is over! The Authomatic.login() either returns None, which means that the login procedure si still pending, or a LoginResult which means that the login procedure is over.
if result:
self.response.write('<a href="..">Home</a>')
Hopefully there is no LoginResult.error but rather the LoginResult.user. Most of the providers don’t provide user info on login. To get more user info we need to call the User.update() method.
if result.error:
self.response.write('<h2>Damn that error: {}</h2>'.format(result.error.message))
elif result.user:
if not (result.user.name and result.user.id):
result.user.update()
Now we can welcome the user by name.
self.response.write('<h1>Hi {}</h1>'.format(result.user.name))
self.response.write('<h2>Your id is: {}</h2>'.format(result.user.id))
self.response.write('<h2>Your email is: {}</h2>'.format(result.user.email))
Seems like we’re done, but we can do more:
If there are credentials the user has logged in with an AuthorizationProvider i.e. OAuth 1.0a or OAuth 2.0 and we can access his/her protected resources.
if result.user.credentials:
Each provider has it’s specific API. Let’s first get the user’s five most recent Facebook statuses.
if result.provider.name == 'fb':
self.response.write('Your are logged in with Facebook.<br />')
Prepare the Facebook Graph API URL.
url = 'https://graph.facebook.com/{}?fields=feed.limit(5)'
url = url.format(result.user.id)
Access the protected resource of the user at that URL.
response = result.provider.access(url)
Parse the response. The Response.data is a data structure (list or dictionary) parsed from the Response.content which usually is JSON.
if response.status == 200:
# Parse response.
statuses = response.data.get('feed').get('data')
error = response.data.get('error')
if error:
self.response.write('Damn that error: {}!'.format(error))
elif statuses:
self.response.write('Your 5 most recent statuses:<br />')
for message in statuses:
text = message.get('message')
date = message.get('created_time')
self.response.write('<h3>{}</h3>'.format(text))
self.response.write('Posted on: {}'.format(date))
else:
self.response.write('Damn that unknown error!<br />')
self.response.write('Status: {}'.format(response.status))
Do the same with Twitter.
if result.provider.name == 'tw':
self.response.write('Your are logged in with Twitter.<br />')
# We will get the user's 5 most recent tweets.
url = 'https://api.twitter.com/1.1/statuses/user_timeline.json'
# You can pass a dictionary of querystring parameters.
response = result.provider.access(url, {'count': 5})
# Parse response.
if response.status == 200:
if type(response.data) is list:
# Twitter returns the tweets as a JSON list.
self.response.write('Your 5 most recent tweets:')
for tweet in response.data:
text = tweet.get('text')
date = tweet.get('created_at')
self.response.write('<h3>{}</h3>'.format(text))
self.response.write('Tweeted on: {}'.format(date))
elif response.data.get('errors'):
self.response.write('Damn that error: {}!'.\
format(response.data.get('errors')))
else:
self.response.write('Damn that unknown error!<br />')
self.response.write('Status: {}'.format(response.status))
That’s it for the Login handler.
Now just for convenience that we don’t have to enter all the URLs manually let’s create a Home handler.
class Home(webapp2.RequestHandler):
def get(self):
Create links to our Login handler.
self.response.write('Login with <a href="login/fb">Facebook</a>.<br />')
self.response.write('Login with <a href="login/tw">Twitter</a>.<br />')
Create OpenID forms where the user can specify his/her claimed ID. The library by default extracts the identifier from the query string id parameter, but you can change its name to whatever you want.
self.response.write('''
<form action="login/oi">
<input type="text" name="id" value="me.yahoo.com" />
<input type="submit" value="Authenticate With OpenID">
</form>
''')
self.response.write('''
<form action="login/gae_oi">
<input type="text" name="id" value="me.yahoo.com" />
<input type="submit" value="Authenticate With GAEOpenID">
</form>
''')
Route URLs to your handlers.
ROUTES = [webapp2.Route(r'/login/<:.*>', Login, handler_method='any'),
webapp2.Route(r'/', Home)]
Finally instantiate the WSGI application.
Don’t forget to set up the app.yaml file.
# app.yaml
application: authomatic-simple-example
version: 0
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /.*
script: main.app
And here is the complete app:
# main.py
import webapp2
from authomatic import Authomatic
from authomatic.adapters import Webapp2Adapter
from config import CONFIG
# Instantiate Authomatic.
authomatic = Authomatic(config=CONFIG, secret='some random secret string')
# Create a simple request handler for the login procedure.
class Login(webapp2.RequestHandler):
# The handler must accept GET and POST http methods and
# Accept any HTTP method and catch the "provider_name" URL variable.
def any(self, provider_name):
# It all begins with login.
result = authomatic.login(Webapp2Adapter(self), provider_name)
# Do not write anything to the response if there is no result!
if result:
# If there is result, the login procedure is over and we can write to response.
self.response.write('<a href="..">Home</a>')
if result.error:
# Login procedure finished with an error.
self.response.write('<h2>Damn that error: {}</h2>'.format(result.error.message))
elif result.user:
# Hooray, we have the user!
# OAuth 2.0 and OAuth 1.0a provide only limited user data on login,
# We need to update the user to get more info.
if not (result.user.name and result.user.id):
result.user.update()
# Welcome the user.
self.response.write('<h1>Hi {}</h1>'.format(result.user.name))
self.response.write('<h2>Your id is: {}</h2>'.format(result.user.id))
self.response.write('<h2>Your email is: {}</h2>'.format(result.user.email))
# Seems like we're done, but there's more we can do...
# If there are credentials (only by AuthorizationProvider),
# we can _access user's protected resources.
if result.user.credentials:
# Each provider has it's specific API.
if result.provider.name == 'fb':
self.response.write('Your are logged in with Facebook.<br />')
# We will access the user's 5 most recent statuses.
url = 'https://graph.facebook.com/{}?fields=feed.limit(5)'
url = url.format(result.user.id)
# Access user's protected resource.
response = result.provider.access(url)
if response.status == 200:
# Parse response.
statuses = response.data.get('feed').get('data')
error = response.data.get('error')
if error:
self.response.write('Damn that error: {}!'.format(error))
elif statuses:
self.response.write('Your 5 most recent statuses:<br />')
for message in statuses:
text = message.get('message')
date = message.get('created_time')
self.response.write('<h3>{}</h3>'.format(text))
self.response.write('Posted on: {}'.format(date))
else:
self.response.write('Damn that unknown error!<br />')
self.response.write('Status: {}'.format(response.status))
if result.provider.name == 'tw':
self.response.write('Your are logged in with Twitter.<br />')
# We will get the user's 5 most recent tweets.
url = 'https://api.twitter.com/1.1/statuses/user_timeline.json'
# You can pass a dictionary of querystring parameters.
response = result.provider.access(url, {'count': 5})
# Parse response.
if response.status == 200:
if type(response.data) is list:
# Twitter returns the tweets as a JSON list.
self.response.write('Your 5 most recent tweets:')
for tweet in response.data:
text = tweet.get('text')
date = tweet.get('created_at')
self.response.write('<h3>{}</h3>'.format(text))
self.response.write('Tweeted on: {}'.format(date))
elif response.data.get('errors'):
self.response.write('Damn that error: {}!'.\
format(response.data.get('errors')))
else:
self.response.write('Damn that unknown error!<br />')
self.response.write('Status: {}'.format(response.status))
# Create a home request handler just that you don't have to enter the urls manually.
class Home(webapp2.RequestHandler):
def get(self):
# Create links to the Login handler.
self.response.write('Login with <a href="login/fb">Facebook</a>.<br />')
self.response.write('Login with <a href="login/tw">Twitter</a>.<br />')
# Create OpenID form where the user can specify his claimed identifier.
# The library by default extracts the identifier from the "id" parameter.
self.response.write('''
<form action="login/oi">
<input type="text" name="id" value="me.yahoo.com" />
<input type="submit" value="Authenticate With OpenID">
</form>
''')
# Create GAEOpenID form
self.response.write('''
<form action="login/gae_oi">
<input type="text" name="id" value="me.yahoo.com" />
<input type="submit" value="Authenticate With GAEOpenID">
</form>
''')
# Create routes.
ROUTES = [webapp2.Route(r'/login/<:.*>', Login, handler_method='any'),
webapp2.Route(r'/', Home)]
# Instantiate the webapp2 WSGI application.
app = webapp2.WSGIApplication(ROUTES, debug=True)