Basecamp oAuth provider for django-allauth

Plan

INTRO

In this article you will see how to create custom provider module for a django-allauth package. I’ll do it on real example of Basecamp provider module I’ve written; it’s already accepted into the codebase of django-allauth on github.

WHAT IS DJANGO-ALLAUTH?

There is always a task to integrate social networks and different platforms into our site. It enables user to login on our site using his existing account on one of the platforms. Historically there were different libraries in Django framework for that. The first good approach in my mind was a django-social-auth that was started nearly 5 years ago and actively maintained till now. I used it a lot and even wrote a Trello provider to for that. However, recently, about 2 years ago, another package started to became more and more popular. The package name is ‘django-allauth’. The major difference from django-social-auth is the fact that it includes plain email-based registration. With django-social-auth, I’ve had to use an additional package named django-registration to implement such functionality. That’s why I’ve switched to django-allauth: it takes less time for me to implement typical register/login functionality, both social and email-based.

HOW OAUTH WORKS

There is general agreement that handles the process of authorization using account from external site. It’s OAuth (Open Auth) protocol.

The general idea is following:

  • User is on our site (www.campscore.io)
  • User clicks on specific link that brings him/her to the Oauth provider site where he/she is registered (www.basecamp.com)
  • Basecamp asks user if User allows to pass details of his/her account (email, name, photo but not the password) to www.campscore.io
  • If user allowed access to his/her details — user is redirected back to www.campscore.io
  • When user is redirected, Basecamp adds specific temporary key to the url. CampScore receives this key and use it to make an internal request to the Basecamp to receive the data.

When we go into details there are two versions of OAauth protocols: OAuth1 and OAuth2. The have differencies that are well explained in this stackoverflow answer:http://stackoverflow.com/questions/4113934/how-is-oauth-2-different-from-oauth-1

We will work with OAuth2 version of protocol as it’s simpler and more convenient to use. I’ll took an image from stackoverflow answer to illustrate how it works in details.

This diagram has more steps that I’ve outlined before. It additionally contains: — exchange key that Basecamp returned us to the Access Token that we can use to access the data.

I have to mention also that developer must create a record on OAuth provider site in order to be able to authorize users. This record contain: name of the app that asks for permission, a name of the company that owns an app, app domain name, url to redirect user when he/she grants access, url to redirect user when he/she did not allow to provide personal information.

The place to create an app usually can be found in developer documentation for OAuth on the OAuth provider site.

For example, basecamp app can be registered here: https://integrate.37signals.com/apps/new. The interface looks like following.

STRUCTURE OF PROVIDER MODULE IN DJANGO-ALLAUTH

Django-allauth provides very handy base classes that do all the routine work. Basically adding a provider is more like configuration then coding.

Base classes you need to reuse are following:

  • It’s base views that handle all redirects and processing responses from partner: OAuth2Adapter, OAuth2LoginView, OAuth2CallbackView located in allauth.socialaccount.providers.oauth2.views
  • And base provider classes that helps us to extract data from the partner: OAuth2Provider located in allauth.socialaccount.providers.oauth2.provider

EXAMPLES OF CODE OF BASECAMP PROVIDER

Best representation of all neccesary changes could be viewed in the changed files of pull-request https://github.com/pennersr/django-allauth/pull/1077/files

There are following changes:

  • docs/providers.rst — add documentation with description where to register oAuth2 app, and where is full set of docs of the provider.
  • test_settings.py — add provider to the settings so when we run tests it automatically excecut e provider tests that we added
  • allauth/socialaccount/providers/basecamp — create a folder for the provider-related files
  • allauth/socialaccount/providers/basecamp/tests.py — add test for the provider
  • allauth/socialaccount/providers/basecamp/provider.py — create provider itself
  • allauth/socialaccount/providers/basecamp/views.py — create adapter for provider
  • allauth/socialaccount/providers/basecamp/urls.py — add adapter urls

Core code is following:

django-allauth/allauth/socialaccount/providers/basecamp/views.py

import requests

from allauth.socialaccount.providers.oauth2.views import (OAuth2Adapter,
OAuth2LoginView,
OAuth2CallbackView)
from .provider import BasecampProvider


class BasecampOAuth2Adapter(OAuth2Adapter):
provider_id = BasecampProvider.id
access_token_url = 'https://launchpad.37signals.com/authorization/token?type=web_server'
authorize_url = 'https://launchpad.37signals.com/authorization/new'
profile_url = 'https://launchpad.37signals.com/authorization.json'

def complete_login(self, request, app, token, **kwargs):
headers = {'Authorization': 'Bearer {0}'.format(token.token)}
resp = requests.get(self.profile_url, headers=headers)
extra_data = resp.json()
return self.get_provider().sociallogin_from_response(request,
extra_data)


oauth2_login = OAuth2LoginView.adapter_view(BasecampOAuth2Adapter)
oauth2_callback = OAuth2CallbackView.adapter_view(BasecampOAuth2Adapter)

django-allauth/allauth/socialaccount/providers/basecamp/provider.py

from allauth.socialaccount import providers
from allauth.socialaccount.providers.base import ProviderAccount
from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider


class BasecampAccount(ProviderAccount):

def get_avatar_url(self):
return None

def to_str(self):
dflt = super(BasecampAccount, self).to_str()
return self.account.extra_data.get('name', dflt)


class BasecampProvider(OAuth2Provider):
id = 'basecamp'
name = 'Basecamp'
package = 'allauth.socialaccount.providers.basecamp'
account_class = BasecampAccount

def get_auth_params(self, request, action):
data = super(BasecampProvider, self).get_auth_params(request, action)
data['type'] = 'web_server'
return data

def extract_uid(self, data):
data = data['identity']
return str(data['id'])

def extract_common_fields(self, data):
data = data['identity']
return dict(
email=data.get('email_address'),
username=data.get('email_address'),
first_name=data.get('first_name'),
last_name=data.get('last_name'),
name="%s %s" % (data.get('first_name'), data.get('last_name')),
)


providers.registry.register(BasecampProvider)

LINK TO PULL REQUEST AND CAMPSSORE

https://github.com/pennersr/django-allauth/pull/1077 — original pull request

http://www.campscore.io/ — A bit about project. It’s a dashboard that provides statistics for you BaseCamp account. You just have to signin with Basecamp account and it will show you amount of open/closed/peding tasks for specific dates you choose. So you can easily track performance of your team or your personal one.

2015–10–09

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.