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 walue in our case will be http://[hostname]/login/fb for Facebook.

# 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

from config import CONFIG

Create the home request handler.

@app.route('/')
def index():
    return render_template('index.html')

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'])
def login(provider_name):

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.

    response = make_response()
    result = authomatic.login(WerkzeugAdapter(request, response), 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.

    if result:
        if result.user:
            result.user.update()
        return render_template('login.html', result=result)

If there is no LoginResult the login procedure is still pending and we need to return the response.

    return response

Finally run the WSGI application.

if __name__ == '__main__':
    app.run(debug=True, port=8080)

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.

{# index.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 successfull.

            {% 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

Here is the complete login.html template. Remember that you can download all the files we just created from GitHub.

{# index.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 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)
Fork me on GitHub