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 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.

# -*- coding: utf-8 -*-
# example/simple/urls.py

from django.conf.urls import url

from . import views

urlpatterns = [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.

# -*- coding: utf-8 -*-
# Django settings for example project.
]

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    # Uncomment the next line for simple clickjacking protection:
    # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

ROOT_URLCONF = 'example.urls'

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

# -*- coding: utf-8 -*-
# example/example/urls.py

from django.conf.urls import include, url
# from django.contrib import admin
    # Uncomment the next line to enable the admin:
    # url(r'^admin/', include(admin.site.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. You 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 https://[hostname]/simple/login/fb for Facebook.

# -*- coding: utf-8 -*-
# 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.

# -*- coding: utf-8 -*-
# example/simple/views.py

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

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.


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


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">

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

    ''')

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.

    response = HttpResponse()

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.


    # Don't write anything to the response if there is no result!

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 there is result, the login procedure is over and we can write to
        response.write('<a href="..">Home</a>')
        if result.error:
        elif result.user:
            # Hooray, we have the user!

Now we can welcome the user by name.

            # We need to update the user to get more info.
            if not (result.user.name and result.user.id):
                result.user.update()

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.

                u'<h2>Your email is: {0}</h2>'.format(result.user.email))

Each provider has it’s specific API. Let’s first get the user’s five most recent Facebook statuses.


            # If there are credentials (only by AuthorizationProvider),

Prepare the Facebook Graph API URL.


                # Each provider has it's specific API.

Access the protected resource of the user at that URL.


Parse the response. The Response.data is a data structure (list or dictionary) parsed from the Response.content which usually is JSON.

                    url = 'https://graph.facebook.com/{0}?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(
                                u'Damn that error: {0}!'.format(error))
                        elif statuses:
                            response.write(
                                'Your 5 most recent statuses:<br />')
                            for message in statuses:

Do the same with Twitter.

                                date = message.get('created_time')

                                response.write(u'<h3>{0}</h3>'.format(text))
                                response.write(u'Posted on: {0}'.format(date))
                    else:
                        response.write('Damn that unknown error!<br />')
                        response.write(u'Status: {0}'.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 isinstance(access_response.data, 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(u'<h3>{0}</h3>'.format(text))

Finally return the response instance.


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

$ python manage.py runserver

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

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

# -*- coding: utf-8 -*-
# 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: {0}</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(u'<h1>Hi {0}</h1>'.format(result.user.name))
            response.write(u'<h2>Your id is: {0}</h2>'.format(result.user.id))
            response.write(
                u'<h2>Your email is: {0}</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/{0}?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(
                                u'Damn that error: {0}!'.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(u'<h3>{0}</h3>'.format(text))
                                response.write(u'Posted on: {0}'.format(date))
                    else:
                        response.write('Damn that unknown error!<br />')
                        response.write(u'Status: {0}'.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 isinstance(access_response.data, 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(u'<h3>{0}</h3>'.format(text))
                                response.write(u'Tweeted on: {0}'.format(date))

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

    return response
Fork me on GitHub