Authomatic
is an authorization / authentication
client library for Python web applications
inspired by Alex Vagin’s Simpleauth.
In fact, I almost named it Deadsimpleauth,
but that name would be too long
for a succinct library.

- Tweet

# 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, 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'],
        },

        '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)

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/{}?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

Most 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/{}?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)