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 Django Example

In this tutorial we will create a simple Django application that will be able to log users in with Facebook, Twitter and OpenID and retrieve their 5 most recent tweets/statuses.

You can download all the source files we are about to create here.

First create a new Django project named example.

    $ django-admin.py startproject example

Inside the newly created example project create a new application named simple.

    $ cd example
    $ python manage.py startapp simple
    $ cd simple

Open the the `example/simple/urls.py` module and map the `/` root URL to the `home` view and the `/login/[provider_name]` URL to the `login` view. We will create the views later.

    # example/simple/urls.py

    from django.conf.urls import patterns, url

    import views

    urlpatterns = patterns('',
        url(r'^$', views.home, name='home'),
        url(r'^login/(\w*)', views.login, name='login')
    )

We also need to include the `simple` app in the `INSTALLED_APPS` tuple in the `example/example/settings.py` module.

    # example/example/settings.py

    INSTALLED_APPS = (
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.sites',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        # Uncomment the next line to enable the admin:
        # 'django.contrib.admin',
        # Uncomment the next line to enable admin documentation:
        # 'django.contrib.admindocs',
        'simple',
    )

And create a route to the app in the `example/example/urls.py` module.

    # example/example/urls.py

    from django.conf.urls import patterns, include, url

    urlpatterns = patterns('',
        url(r'^simple/', include('simple.urls')),
    )

Now in the `example/simple` directory create the `config.py` module with 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 view which we will create in this tutorial and whose walue in our case will be `http://[hostname]/simple/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,
        }
    }

The rest happens in the `example/simple/views.py` file. Open it and import what’s needed.

    # example/simple/views.py

    from django.http import HttpResponse
    from authomatic import Authomatic
    from authomatic.adapters import DjangoAdapter

    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, 'a super secret random string')

Create the home view with an OpenID form and links to the login view which we are going to create next.

    def home(request):
        return HttpResponse('''
            Login with <a href="login/fb">Facebook</a>.<br />
            Login with <a href="login/tw">Twitter</a>.<br />
            <form action="login/oi">
                <input type="text" name="id" value="me.yahoo.com" />
                <input type="submit" value="Authenticate With OpenID">
            </form>
        ''')

Create the login view which should recieve the `provider_name` URL variable. Inside the view instantiate the `django.http.HttpResponse` class.

    def login(request, provider_name):
        response = HttpResponse()

Now just Log the user in by calling the `Authomatic.login()` method inside the login view. You must pass it the `DjangoAdapter` and the `provider_name` URL variable. The `DjangoAdapter` needs instances of `django.http.HttpRequest` and `django.http.HttpResponse`. The method will redirect the user to the specified provider to prompt him/her for consent and redirect him/her back to this view.

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

                response.write('<h1>Hi {}</h1>'.format(result.user.name))
                response.write('<h2>Your id is: {}</h2>'.format(result.user.id))
                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':
                        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.

                        access_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 access_response.status == 200:
                            # Parse response.
                            statuses = access_response.data.get('feed').get('data')
                            error = access_response.data.get('error')

                            if error:
                                response.write('Damn that error: {}!'.format(error))
                            elif statuses:
                                response.write('Your 5 most recent statuses:<br />')
                                for message in statuses:

                                    text = message.get('message')
                                    date = message.get('created_time')

                                    response.write('<h3>{}</h3>'.format(text))
                                    response.write('Posted on: {}'.format(date))
                        else:
                            response.write('Damn that unknown error!<br />')
                            response.write('Status: {}'.format(response.status))

Do the same with Twitter.

                    if result.provider.name == 'tw':
                        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.
                        access_response = result.provider.access(url, {'count': 5})

                        # Parse response.
                        if access_response.status == 200:
                            if type(access_response.data) is list:
                                # Twitter returns the tweets as a JSON list.
                                response.write('Your 5 most recent tweets:')
                                for tweet in access_response.data:
                                    text = tweet.get('text')
                                    date = tweet.get('created_at')

                                    response.write('<h3>{}</h3>'.format(text))
                                    response.write('Tweeted on: {}'.format(date))

                            elif response.data.get('errors'):
                                response.write('Damn that error: {}!'.\
                                                    format(response.data.get('errors')))
                        else:
                            response.write('Damn that unknown error!<br />')
                            response.write('Status: {}'.format(response.status))

Finally return the `response` instance.

        return response

Run the app and navigate to `http://[hostname]/simple` in your browser.

    $ python manage.py runserver

And here is the complete `example/simple/views.py` module. Remember that you can download all the files we just created from GitHub.

    # example/simple/views.py

    from django.http import HttpResponse
    from authomatic import Authomatic
    from authomatic.adapters import DjangoAdapter

    from config import CONFIG

    authomatic = Authomatic(CONFIG, 'a super secret random string')

    def home(request):
        # Create links and OpenID form to the Login handler.
        return HttpResponse('''
            Login with <a href="login/fb">Facebook</a>.<br />
            Login with <a href="login/tw">Twitter</a>.<br />
            <form action="login/oi">
                <input type="text" name="id" value="me.yahoo.com" />
                <input type="submit" value="Authenticate With OpenID">
            </form>
        ''')

    def login(request, provider_name):
        # We we need the response object for the adapter.
        response = HttpResponse()

        # Start the login procedure.
        result = authomatic.login(DjangoAdapter(request, response), provider_name)

        # If there is no result, the login procedure is still pending.
        # Don't 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.
            response.write('<a href="..">Home</a>')

            if result.error:
                # Login procedure finished with an error.
                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.
                response.write('<h1>Hi {}</h1>'.format(result.user.name))
                response.write('<h2>Your id is: {}</h2>'.format(result.user.id))
                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':
                        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.
                        access_response = result.provider.access(url)

                        if access_response.status == 200:
                            # Parse response.
                            statuses = access_response.data.get('feed').get('data')
                            error = access_response.data.get('error')

                            if error:
                                response.write('Damn that error: {}!'.format(error))
                            elif statuses:
                                response.write('Your 5 most recent statuses:<br />')
                                for message in statuses:

                                    text = message.get('message')
                                    date = message.get('created_time')

                                    response.write('<h3>{}</h3>'.format(text))
                                    response.write('Posted on: {}'.format(date))
                        else:
                            response.write('Damn that unknown error!<br />')
                            response.write('Status: {}'.format(response.status))

                    if result.provider.name == 'tw':
                        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.
                        access_response = result.provider.access(url, {'count': 5})

                        # Parse response.
                        if access_response.status == 200:
                            if type(access_response.data) is list:
                                # Twitter returns the tweets as a JSON list.
                                response.write('Your 5 most recent tweets:')
                                for tweet in access_response.data:
                                    text = tweet.get('text')
                                    date = tweet.get('created_at')

                                    response.write('<h3>{}</h3>'.format(text))
                                    response.write('Tweeted on: {}'.format(date))

                            elif response.data.get('errors'):
                                response.write('Damn that error: {}!'.\
                                                    format(response.data.get('errors')))
                        else:
                            response.write('Damn that unknown error!<br />')
                            response.write('Status: {}'.format(response.status))

        return response