blog

Django logo

Tutorial: Adding Facebook/Twitter/Google Authentication to a Django Application

by

I needed to add Facebook authentication to a Django app today, and instead of writing it directly against the Facebook API (or re-implementing the OAuth2 dance again), I decided to look around and see if there’s a pre-packaged solution for this common task. Turns out, there’s an excellent project called Python Social Auth, and it covers pretty much any social website with an authentication API.

As it often happens with amazing open-source projects, the documentation is somewhat minimalistic. One key piece that I could not find was a tutorial. This post is aiming to fill that gap. In this tutorial, we will use Facebook, Twitter and Google, as the most common social APIs, but we could easily substitute them with LinkedIn, Yahoo, Forsquare, or a bunch of other providers supported by Python Social Auth library.

If you are comfortable with Django, feel free to skip to Step2.

Step 0. Start a simple Django project

Let’s begin with a barebones Django project named “thirdauth”, named in honour of third-party authentication.

$ django-admin.py startproject thirdauth
$ tree thirdauth/
thirdauth/
├── manage.py
└── thirdauth
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

Running ./manage.py syncdb and then ./manage.py runserver and navigating to localhost:8000 will show the familiar “It worked!” Django page. Let’s put some custom application code in place, so that we can tell whether the current user is authenticated or anonymous.

Step 1. Show current user’s authentication status

Let’s throw together a simple basic page, add CSS, JavaScript and fonts from Twitter Bootstrap, and add a view for home page. For this tutorial, we won’t need custom models or any other views.

NOTE: if you need help with Django views, templates and settings, please check out the Django tutorial.

Another NOTE: Bootstrap is Twitter’s basic set of CSS and JavaScript to help make even minimal web interfaces look and behave consistently well. You do not have to use it for this tutorial, it adds only aesthetic side – it adds a polished feel to things. Bootstrap can be downloaded from http://getbootstrap.com/

Now, the very small customizations we’ll add are:

  • Add ‘thirdauth’ to INSTALLED_APPS
  • Create the template for the home page
  • Add a view for the home page
  • Add a URL pointing to the home page view

Relevant portion of settings.py:

INSTALLED_APPS = (
  'django.contrib.admin',
  'django.contrib.auth',
  ...
  <b>'thirdauth',</b>
)

Template: thirdauth/base.html:

<!DOCTYPE html>
<html lang="en">
 <head>
   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <title>{% block title %}Third-party Authentication Tutorial{% endblock %}</title>
   <!-- Bootstrap -->
   <link href="/static/css/bootstrap.min.css" rel="stylesheet">
   <link href="/static/css/bootstrap-theme.min.css" rel="stylesheet">
   <link href="/static/css/fbposter.css" rel="stylesheet">
   <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
   <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
   <!--[if lt IE 9]>
     <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
     <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
   <![endif]-->
 </head>
 <body>
   {% block main %}{% endblock %}
   <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
   <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
   <!-- Include all compiled plugins (below), or include individual files as needed -->
   <script src="/static/js/bootstrap.min.js"></script>
 </body>
</html>

Template: thirdauth/home.html:

{% extends 'thirdauth/base.html' %}
{% block main %}
 <div>
 <h1>Third-party authentication demo</h1>
 <p>
   {% if user and not user.is_anonymous %}
     Hello {{ user.get_full_name|default:user.username }}!
   {% else %}
     I don’t think we’ve met before.
   {% endif %}
 </p>
 </div>
{% endblock %}

File views.py:

from django.shortcuts import render_to_response
from django.template.context import RequestContext
def home(request):
   context = RequestContext(request,
                           {'user': request.user})
   return render_to_response('thirdauth/home.html',
                             context_instance=context)

File urls.py:

from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
   url(r'^$', 'thirdauth.views.home', name='home'),
   url(r'^admin/', include(admin.site.urls)),
)

Now, when we refresh the page, we will see:

Fair enough – we have not authenticated yet. To make sure our identity-checking template works, try logging in to Django admin with the default Django authentication (assuming you created an admin account on Step 0). You should see a different message: “Hello admin!”, or something like that.

Step 2. Install Python Social Auth

First, let’s add it to our virtualenv:

pip install python-social-auth

Second, let’s make some modifications to our settings.py to include python-social-auth in our project:

INSTALLED_APPS = (
   ...
   'social.apps.django_app.default',
   ...
)
TEMPLATE_CONTEXT_PROCESSORS = (
   'django.contrib.auth.context_processors.auth',
   'django.core.context_processors.debug',
   'django.core.context_processors.i18n',
   'django.core.context_processors.media',
   'django.core.context_processors.static',
   'django.core.context_processors.tz',
   'django.contrib.messages.context_processors.messages',
   'social.apps.django_app.context_processors.backends',
   'social.apps.django_app.context_processors.login_redirect',
)
AUTHENTICATION_BACKENDS = (
   'social.backends.facebook.FacebookOAuth2',
   'social.backends.google.GoogleOAuth2',
   'social.backends.twitter.TwitterOAuth',
   'django.contrib.auth.backends.ModelBackend',
)

Let’s update the urls module to include the new group of URLs:

urlpatterns = patterns('',
...
url('', include('social.apps.django_app.urls', namespace='social')),
...
)

And finally, let’s update the database models:

./manage.py syncdb

Now, if we runserver again, and navigate to Django admin, we’ll see three new tables: Associations, Nonces, and User social auths. This last one will contain the records of our users’ social accounts when they use social networks to authenticate from our app.

We are almost there. Let’s add some links for logging in and logging out, and then we’ll start adding application IDs for social apps.

Step 3. Add links for logging in and logging out.

Since we’ll be logging in and out multiple times, let’s include django.contrib.auth URLs into our URLs configuration:

urlpatterns = patterns('',
   ...
   url('', include('django.contrib.auth.urls', namespace='auth')),
   ...
)

Let’s modify our Home page template like this:

{% extends 'thirdauth/base.html' %}
{% block main %}
 <div>
 <h1>Third-party authentication demo</h1>
 <p>
   <ul>
   {% if user and not user.is_anonymous %}
     <li>
       <a>Hello {{ user.get_full_name|default:user.username }}!</a>
     </li>
     <li>
       <a href="{% url 'auth:logout' %}?next={{ request.path }}">Logout</a>
     </li>
   {% else %}
     <li>
       <a href="{% url 'social:begin' 'facebook' %}?next={{ request.path }}">Login with Facebook</a>
     </li>
     <li>
       <a href="{% url 'social:begin' 'google-oauth2' %}?next={{ request.path }}">Login with Google</a>
     </li>
     <li>
       <a href="{% url 'social:begin' 'twitter' %}?next={{ request.path }}">Login with Twitter</a>
     </li>
   {% endif %}
   </ul>
 </p>
 </div>
{% endblock %}

For the login and logout links in this template to work correctly, we need to modify a few things. First, let’s take care of logout, it’s easier. Just add ‘request’ to the context object that we pass into template-rendering code. Updated views.py:

from django.shortcuts import render_to_response
from django.template.context import RequestContext
def home(request):
   context = RequestContext(request,
                           {'request': request,
                            'user': request.user})
   return render_to_response('thirdauth/home.html',
                             context_instance=context)

For login to work, let’s first add a LOGIN_REDIRECT_URL parameter to settings (to prevent the default /account/profile from raising a 404):

LOGIN_REDIRECT_URL = '/'

And then start adding API-specific parameters for social networks. Right this moment, if you click on any of the “login with X” links, you’ll get redirected to the corresponding social site, but will get an error about invalid client ID. That’s because we have not provided any client IDs yet.

Step 4. Get Client IDs for the social sites.

For all the social networks we are using in this demo, the process of obtaining an OAuth2 client ID (also known as application ID) is pretty similar. All of them will require that your application has a “real” URL – that is, not http://127.0.0.1 or http://localhost. You can add an entry in your /etc/hosts file that maps 127.0.0.1 to something like “test1.com”, and the URL of your application becomes http://test1.com:8000 – that is good enough for testing. You can change it in the social app settings when it goes into production.

Facebook

  • Go to https://developers.facebook.com/apps/?action=create and click the green “Create New App” button.
  • In the settings of the newly-created application, click “Add Platform”. From the options provided, choose Web, and fill in the URL of the site (http://test1.com:8000 in our example).
  • Copy the App ID and App Secret, and place them into settings.py file:
    SOCIAL_AUTH_FACEBOOK_KEY = …
    SOCIAL_AUTH_FACEBOOK_SECRET = …
  • This should be enough to get your app to login with Facebook! Try logging in and out – you should get redirected between your app and FB OAuth2 service, and a new record in the User social auths table will get created, along with a new User record pointing to it.

Google

  • Go to https://console.developers.google.com/ and create a new application.
  • Under APIs and Auth > Credentials, create a new Client ID.
  • Make sure to specify the right callback URL: http://test1.com:8000/complete/google-oauth2/
  • Copy the values into settings file:
    SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = …
    SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = …

Twitter

  • Go to https://apps.twitter.com/app/new and create the new application
  • The callback URL should be something like http://test1.com:8000/complete/twitter/
  • Copy the values into settings.py:
    SOCIAL_AUTH_TWITTER_KEY = …
    SOCIAL_AUTH_TWITTER_SECRET = …

What’s next?

Python Social Auth can provide more than just authentication. By customizing pipeline, we can get account information for our users, manage accounts through email confirmation, and more. But those topics are beyond the scope of this tutorial.

+ more