Using LinkedIn Authentication with Django on Heroku
To build the LinkedIn Today SMS Notification System, I needed to find a framework where I could do the following:
- Integrate LinkedIn OAuth with a more general user management system
- Post the system to a public site
- Handle POST requests from external clients
For this set of criteria, I chose to use Django and publish it using Heroku. This post is similar to the Django Getting Started Guide for Heroku, but it’s designed for this specific use case – Using LinkedIn OAuth as the login system and integrating it with the Django user management system. The code for this example is available here.
Here are the prerequisites for this tutorial:
- Python, virtualenv, pip, and the Heroku toolbelt as described in Getting Started with Python on Heroku/Cedar.
- An installed version of Postgres to test locally
- A Postgres user and a database to use for Django (in this case I’m using ‘linkedin’)
- A LinkedIn API Key. If you haven’t worked with the LinkedIn API before, you’re encouraged to go through the Quick Start Guide.
Create Your Django Environment
% mkdir hellodjango && cd hellodjango
% virtualenv --no-site-packages vent
% source venv/bin/activate
% pip install Django psycopg2 oauth2 simplejson
% django-admin.py startproject hellodjango
% cd hellodjango
% python manage.py startapp linkedin
Now that you’ve done all these pieces, you have a working django system.
File Setup
In order to add the LinkedIn integration, you’ll need to edit the settings and URL files. I’ve created a GitHub repository with the code needed for this example, and you can use that as your model – but I’ll walk through the changes here so you understand how they work.
hellodjango/settings.py
Edit the DATABASES section to configure it to use postgres, and your database:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'linkedin',
'USER': 'postgres',
'PASSWORD': 'xxx',
'HOST': '',
'PORT': '5432',
}
}
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'linkedin'
# Uncomment the next line to enable the admin:
# 'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
)
And finally, you need to tell the application you want to use LinkedIn for authentication and set up your credentials (this can go anywhere in the settings.py file)
AUTH_PROFILE_MODULE= 'linkedin.UserProfile' LINKEDIN_TOKEN='xxx' LINKEDIN_SECRET='xxx' LOGIN_URL='/login/'
hellodjango/urls.py
You also need to update the urls.py file to add the LinkedIn authentication pages.
url(r'^login/?$', 'linkedin.views.oauth_login'), url(r'^logout/?$', 'linkedin.views.oauth_logout'), url(r'^login/authenticated/?$', 'linkedin.views.oauth_authenticated'), url(r'^$','linkedin.views.home'),
hellodjango/linkedin/models.py
Using LinkedIn Auth means we’ll need to store the user’s token and secret for later requests:
from django.db import models from django.contrib.auth.models import User class UserProfile(models.Model): user = models.ForeignKey(User) oauth_token = models.CharField(max_length=200) oauth_secret = models.CharField(max_length=200)
hellodjango/linkedin/views.py
I’m including the code here, although it’ll be easier to just get it from GitHub. This code demonstrates the full OAuth login flow, creating a new Django user using the response, and subsequently making a request using the stored information (home).
# Python import oauth2 as oauth import cgi import simplejson as json import datetime import re
# Django from django.http import HttpResponse from django.shortcuts import render_to_response from django.http import HttpResponseRedirect from django.conf import settings from django.contrib.auth import authenticate, login, logout from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required
# Project from linkedin.models import UserProfile
# from settings.py consumer = oauth.Consumer(settings.LINKEDIN_TOKEN, settings.LINKEDIN_SECRET) client = oauth.Client(consumer) request_token_url = 'https://api.linkedin.com/uas/oauth/requestToken' access_token_url = 'https://api.linkedin.com/uas/oauth/accessToken' authenticate_url = 'https://www.linkedin.com/uas/oauth/authenticate' # /login def oauth_login(request):
# Step 0. Get the current hostname and port for the callback
if request.META['SERVER_PORT'] == 443:
current_server = "https://" + request.META['HTTP_HOST']
else:
current_server = "http://" + request.META['HTTP_HOST']
oauth_callback = current_server + "/login/authenticated"
# Step 1. Get a request token from Provider.
resp, content = client.request("%s?oauth_callback=%s" % (request_token_url,oauth_callback), "GET")
if resp['status'] != '200':
raise Exception("Invalid response from Provider.")
# Step 2. Store the request token in a session for later use.
request.session['request_token'] = dict(cgi.parse_qsl(content))
# Step 3. Redirect the user to the authentication URL.
url = "%s?oauth_token=%s" % (authenticate_url,
request.session['request_token']['oauth_token'])
print url
return HttpResponseRedirect(url)
# / (requires login) @login_required def home(request):
html = "<html><body>"
token = oauth.Token(request.user.get_profile().oauth_token,request.user.get_profile().oauth_secret)
client = oauth.Client(consumer,token)
headers = {'x-li-format':'json'}
url = "http://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline)"
resp, content = client.request(url, "GET", headers=headers)
profile = json.loads(content)
html += profile['firstName'] + " " + profile['lastName'] + "<br/>" + profile['headline']
return HttpResponse(html)
# /logout (requires login) @login_required def oauth_logout(request):
# Log a user out using Django's logout function and redirect them
# back to the homepage.
logout(request)
return HttpResponseRedirect('/')
#/login/authenticated/
def oauth_authenticated(request):
# Step 1. Use the request token in the session to build a new client.
token = oauth.Token(request.session['request_token']['oauth_token'],
request.session['request_token']['oauth_token_secret'])
if 'oauth_verifier' in request.GET:
token.set_verifier(request.GET['oauth_verifier'])
client = oauth.Client(consumer, token)
# Step 2. Request the authorized access token from Provider.
resp, content = client.request(access_token_url, "GET")
if resp['status'] != '200':
print content
raise Exception("Invalid response from Provider.")
access_token = dict(cgi.parse_qsl(content))
headers = {'x-li-format':'json'}
url = "http://api.linkedin.com/v1/people/~:(id,first-name,last-name,industry)"
token = oauth.Token(access_token['oauth_token'],
access_token['oauth_token_secret'])
client = oauth.Client(consumer,token)
resp, content = client.request(url, "GET", headers=headers)
profile = json.loads(content)
# Step 3. Lookup the user or create them if they don't exist.
firstname = profile['firstName']
lastname = profile['lastName']
identifier = profile['id']
try:
user = User.objects.get(username=identifier)
except User.DoesNotExist:
user = User.objects.create_user(identifier,
'%s@linkedin.com' % identifier,
access_token['oauth_token_secret']) user.first_name = firstname
user.last_name = lastname
user.save()
# Save our permanent token and secret for later.
userprofile = UserProfile()
userprofile.user = user
userprofile.oauth_token = access_token['oauth_token']
userprofile.oauth_secret = access_token['oauth_token_secret']
userprofile.save()
# Authenticate the user and log them in using Django's pre-built
# functions for these things.
user = authenticate(username=identifier,
password=access_token['oauth_token_secret'])
login(request, user)
return HttpResponseRedirect('/')
For any future requests, you’ll grab the user’s token and secret from the profile to make the request just like what’s shown in the “home” example above. I’ll go into this in more detail in the following posts, on creating scheduled requests and handling POST requests from external systems.
Procfile
This is needed at the top level (same level as the hellodjango project directory) to tell Heroku how to run your application.
web: python hellodjango/manage.py runserver "0.0.0.0:$PORT"
.gitignore
Ignore the venv directory and compiled python files
venv *.pyc
Test Django Server Locally
Ok, now that you’ve gotten everything all set up (or grabbed it from github), you’re ready to run the server and make sure it works correctly.
% python manage.py syncdb % python manage.py runserver
This should bring up a server on localhost which does the basic authentication dance and presents the user’s name and headline (so exciting!)
Deploy to Heroku
Finally, we can get to the Heroku deployment. Again, this is taken almost verbatim from the Getting Started With Django document on Heroku, so refer to that for more information on what’s happening.
% git init % git add . % git commit -m "My LinkedIn Django App" % heroku create --stack cedar % git push heroku master % heroku run python hellodjango/manage.py syncdb % heroku open
This should give you the same experience on Heroku that you have on your local system – the user is sent to LinkedIn to log in, they’re added to the system and presented a fascinating page with their basic information.
I’ve made this example as simple as possible to make it easy to integrate into your Django Application without unnecessary overhead. Comments, clarifications or other questions welcome.

[...] working Django system on Heroku. If you set up my example LinkedIn Django application from this post, you’ll be able to extend that [...]
[...] Integrate Log in with LinkedIn into Django and deploy to Heroku [...]