Simple Flask Example¶
In this tutorial we will create a simple Flask application
which will use Authomatic with WerkzeugAdapter
to log users in with Facebook, Twitter and OpenID
and retrieve their recent tweets/statuses.
You can download all the source files we are about to create here.
First create the Config dictionary where you set up all the providers you want to use.
Yo will need the consumer_key
and consumer_secret
which you can get
here for Facebook and
here for Twitter.
Note
Facebook and other OAuth 2.0 providers require a redirect URI
which should be the URL of the login request handler
which we will create in this tutorial and whose value in our case will be
https://[hostname]/login/fb
for Facebook.
# -*- coding: utf-8 -*-
# config.py
from authomatic.providers import oauth2, oauth1, openid
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'],
},
'oi': {
# OpenID provider dependent on the python-openid package.
'class_': openid.OpenID,
}
}
Create main.py
file and import what’s needed.
from flask import Flask, render_template, request, make_response
from authomatic.adapters import WerkzeugAdapter
from authomatic import Authomatic
Create the home request handler.
@app.route('/')
Create the login request handler which should receive the provider_name
URL variable and
should also accept the POST
HTTP method to work properly with the openid.OpenID
provider which we have configured in the Config.
@app.route('/login/<provider_name>/', methods=['GET', 'POST'])
Now just Log the user in by calling the Authomatic.login()
method inside the login handler.
You must pass it the WerkzeugAdapter
and the provider_name
URL variable.
The WerkzeugAdapter
needs instances of werkzeug.wrappers.Request
and werkzeug.wrappers.Response
classes.
The handler must return the response
so we assign it to a variable.
# We need response object for the WerkzeugAdapter.
# Log the user in, pass it the adapter and the provider name.
Note
If you want to use the flask.session
, you can do it like this:
# You need to set a secret string otherwise the session will not work.
app.secret_key = 'random secret string'
result = authomatic.login(
WerkzeugAdapter(request, response),
provider_name,
session=session,
session_saver=lambda: app.save_session(session, response)
)
Now check whether there is a LoginResult
.
If so, we need to update the User
to get his/her info.
Then just pass the whole LoginResult
to the template.
request,
response),
if result.user:
If there is no LoginResult
the login procedure is still pending and
we need to return the response
.
Finally run the WSGI application.
# Don't forget to return the response.
return response
The rest happens in the templates.
First create a base template.
{# base.html #}
<!DOCTYPE html>
<html>
<head>
<title>Authomatic Flask Example</title>
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>
The index.html
template contains just links to the login handler and
an OpenID form. The Authomatic.login()
method expects the claimed ID to be in the
id
request parameter. This off course can be changed.
{# index.html #}
{% extends "base.html" %}
{% block body %}
<!--Links to the login handler-->
Login with <a href="login/fb">Facebook</a>.<br />
Login with <a href="login/tw">Twitter</a>.<br />
<!--OpenID form-->
<form action="login/oi">
<input type="text" name="id" value="me.yahoo.com" />
<input type="submit" value="Authenticate With OpenID">
</form>
{% endblock %}
The most interesting things happen in the login.html
template.
First create a link to the home handler.
{# login.html #}
{% extends "base.html" %}
{% block body %}
<a href="/">Home</a>
Then check for possible errors.
{% if result.error %}
<h2>Damn that error: {{ result.error.message }}</h2>
{% endif %}
And welcome the user.
{% if result.user %}
<h1>Hi {{ result.user.name }}</h1>
<h2>Your id is: {{ result.user.id }}</h2>
<h2>Your email is: {{ result.user.email }}</h2>
{% endif %}
Seems like we’re done, but we can do more.
If the user has Credentials
, we can access his/her protected resources.
{% if result.user.credentials %}
Let’s get the user’s five most recent Facebook statuses first.
{% if result.provider.name == 'fb' %}
Your are logged in with Facebook.<br />
Prepare the Facebook API URL.
{% set url = 'https://graph.facebook.com/{0}?fields=feed.limit(5)'.format(result.user.id) %}
And access the protected resource at that URL. The call returns a Response
instance.
{% set response = result.provider.access(url) %}
Check whether the response was successful.
{% if response.status == 200 %}
Parse the Response.data
attribute which is a data structure (dict
or list
)
parsed from the Response.content
which usually is JSON.
Handle possible errors.
{% if response.data.error %}
Damn that error: {{ response.data.error }}!
{% endif %}
If there are no errors, there should be a list
of statuses in the response.data.feed.data
node.
{% if response.data.feed.data %}
Your 5 most recent statuses:<br />
{% for status in response.data.feed.data %}
<h3>{{ status.message or status.name or status.story }}</h3>
Posted on: {{ status.created_time }}
{% endfor %}
{% endif %}
Close the opened {% if %}
tags.
{% endif %}{# response.status == 200 #}
{% endif %}{# result.provider.name == 'fb' #}
Do the same for Twitter.
{% if result.provider.name == 'tw' %}
Your are logged in with Twitter.<br />
{% set url = 'https://api.twitter.com/1.1/statuses/user_timeline.json?count=5' %}
{% set response = result.provider.access(url) %}
{% if response.status == 200 %}
{% if response.data.errors %}
Damn that error: {{ response.data.errors }}!
{% endif %}
{% if response.data %}
Your 5 most recent tweets:<br />
{% if response.data %}
Your 5 most recent tweets:<br />
{% for tweet in response.data %}
<h3>{{ tweet.text }}</h3>
Posted on: {{ tweet.created_at }}
{% endfor %}
{% endif %}
{% endif %}
{% endif %}{# result.provider.name == 'tw' #}
Close the opened {% if result.user.credentials %}
tag and the body
block.
{% endif %}{# result.user.credentials #}
{% endblock body %}
That’s it. Now just run the application.
$ python main.py
Tip
Some of the providers don’t support authorization from apps running on localhost.
Probably the best solution is to use an arbitrary domain as an alias of the 127.0.0.1
IP address.
You can do this on UNIX systems by adding an alias to the /etc/hosts
file.
# /etc/hosts
127.0.0.1 localhost
127.0.0.1 yourlocalhostalias.com
You can do this on Windows systems by adding an alias in the C:\Windows\system32\drivers\etc\hosts
file.
127.0.0.1 yourlocalhostalias.com
Here is the complete login.html
template.
Remember that you can download all the files we just created from
GitHub.
{# login.html #}
{% extends "base.html" %}
{% block body %}
<a href="/">Home</a>
{# Check for errors. #}
{% if result.error %}
<h2>Damn that error: {{ result.error.message }}</h2>
{% endif %}
{# Welcome the user. #}
{% if result.user %}
<h1>Hi {{ result.user.name }}</h1>
<h2>Your id is: {{ result.user.id }}</h2>
<h2>Your email is: {{ result.user.email }}</h2>
{% endif %}
{# If the user has credentials, we can access his/her protected resources. #}
{% if result.user.credentials %}
{# Let's get the user's 5 most recent statuses. #}
{% if result.provider.name == 'fb' %}
Your are logged in with Facebook.<br />
{# Insert the user's ID to the URL and access the resource. #}
{% set url = 'https://graph.facebook.com/{0}?fields=feed.limit(5)'.format(result.user.id) %}
{% set response = result.provider.access(url) %}
{# Parse the response. #}
{% if response.status == 200 %}
{% if response.data.error %}
Damn that error: {{ response.data.error }}!
{% endif %}
{% if response.data.feed.data %}
Your 5 most recent statuses:<br />
{% for status in response.data.feed.data %}
<h3>{{ status.message or status.name or status.story }}</h3>
Posted on: {{ status.created_time }}
{% endfor %}
{% endif %}
{% endif %}{# response.status == 200 #}
{% endif %}{# result.provider.name == 'fb' #}
{# Do the same for Twitter. #}
{% if result.provider.name == 'tw' %}
Your are logged in with Twitter.<br />
{% set url = 'https://api.twitter.com/1.1/statuses/user_timeline.json?count=5' %}
{% set response = result.provider.access(url) %}
{% if response.status == 200 %}
{% if response.data.errors %}
Damn that error: {{ response.data.errors }}!
{% endif %}
{% if response.data %}
Your 5 most recent tweets:<br />
{% for tweet in response.data %}
<h3>{{ tweet.text }}</h3>
Posted on: {{ tweet.created_at }}
{% endfor %}
{% endif %}
{% endif %}
{% endif %}{# result.provider.name == 'tw' #}
{% endif %}{# result.user.credentials #}
{% endblock body %}
And here is the whole main.py
module.
# -*- coding: utf-8 -*-
"""
This is a simple Flask app that uses Authomatic to log users in with Facebook
Twitter and OpenID.
"""
from flask import Flask, render_template, request, make_response
from authomatic.adapters import WerkzeugAdapter
from authomatic import Authomatic
from config_template import CONFIG
app = Flask(__name__)
# Instantiate Authomatic.
authomatic = Authomatic(CONFIG, 'your secret string', report_errors=False)
@app.route('/')
def index():
"""
Home handler.
"""
return render_template('index.html')
@app.route('/login/<provider_name>/', methods=['GET', 'POST'])
def login(provider_name):
"""
Login handler, must accept both GET and POST to be able to use OpenID.
"""
# We need response object for the WerkzeugAdapter.
response = make_response()
# Log the user in, pass it the adapter and the provider name.
result = authomatic.login(
WerkzeugAdapter(
request,
response),
provider_name)
# If there is no LoginResult object, the login procedure is still pending.
if result:
if result.user:
# We need to update the user to get more info.
result.user.update()
# The rest happens inside the template.
return render_template('login.html', result=result)
# Don't forget to return the response.
return response
# Run the app.
if __name__ == '__main__':
app.run(debug=True, port=8080)