Greg Aker

oAuth & the 37 Signals API

Filed in: Python, Django

February 25, 2012

I started what was supposed to be a quick and fun Django project using the Basecamp API. The lack of documentation in their API makes me want to punch a litter of puppies. (This is a joke, puppies are the best)

To help prevent you from punching puppies and rather than trudge through their APIs Google Group, here's what I've figured out. I'm using Python Requests because it's fantastic. If you don't use it stop the insanity, and make your life easier by using it. Additionally, my code examples are within the context of Django, but you should be able to translate them to your language of choice..

First off, read the oAuth spec. Next we can build a simple django view to handle the initial authentication.

Get Auth Code

import requests
import urllib
from django import http
from django.conf import settings

def auth(request):
    BC_CLIENT_ID = getattr(settings, 'BASECAMP_CLIENT_ID', None)
    BC_CLIENT_SECRET = getattr(settings, 'BASECAMP_CLIENT_SECRET', None)
    BC_REDIRECT_URI = getattr(settings, 'BASECAMP_REDIRECT_URI', None)

    launchpad_base_url = 'https://launchpad.37signals.com/'
    query_args = {
        'type': self.auth_type,
        'client_id': client_id,
        'redirect_uri': redirect_uri
    }
    url = '{0}authorization/new?{1}'.format(
        launchpad_base_url,
        urllib.urlencode(self.query_args))
    return http.HttpResponseRedirect(url)

Sweet so now the user is going to be redirected to the 37 Signals Launchpad to authenticate. They will then be directed back to your website to the BC_REDIRECT_URI that was defined above. We get that code GET parameter, and then submit a POST request to get the auth token.

Get Access Token

import json
import requests
from django import http

def get_auth_token(request):
    code = request.GET.get('code')
    if not code:
        raise http.Http404

    query_args = {
        'type': self.auth_type,
        'client_id': client_id,
        'redirect_uri': redirect_uri
    }
    url = "https://launchpad.37signals.com/authorization/token"
    req = requests.post(url, data=self.query_args)
    if req.status_code != 200:
        # raise some kind of exception or do something

    data = json.loads(req.content)

    # do other things & `render_to_response()`

At this point, data is going to be a Python dictionary consisting of the following:

You'll want to save these in the user's session, or perhaps to the database. What ever you think is best.

Make Authorized Requests

Sweet, so now you have the users access_token. You can let the user do things with it now. For instance:

Get accounts the user belongs to

Doing this, you can get the id and urls (eg: https://example.basecamphq.com) to the users basecamp services.

import json
import requests
from django import http

def some_view(request):
    # Get token from the session
    access_token = request.session.get('access_token')
    url = 'https://launchpad.37signals.com/authorization.json?access_token={1}.format(
        access_token)
    req = reqeusts.get(url)
    if req.status_code != 200:
        # do something if it's a bad request

    data = json.loads(req.content)
    return http.HttpResponse(data)

Authenticated calls to the Basecamp API

At this point, we can make calls to each projects API urls. example.basecamphq.com/me.xml

You need to set a custom auth header with the access_token. Why it's not easy like the Facebook API, I don't know. But anyhoo, here's what you'd do:

import requests

url = "https://foobar.basecamphq.com/me.xml"
headers = {
    "Authorization": 'Bearer "{0}"'.format(access_token)
}
req = requests.get(url, headers=headers)
print req.content

Note You can substitute .json for .xml on some of these calls. Not all of them work though, and they don't officially support json. So rock out like it's 2006 and enjoy that XML!

Conclusion

APIs can be wonderful like GitHub/Twitter/Facebook/Instagram, or be a nightmare like in our case here. I think a lot comes down to documentation. Does lack of documentation mean they don't want you using it? I don't think the fact that the oAuth spec is kind of a moving target is an excuse to roll something out and provide no definitive documentation.

The wonderful APIs I mentioned above have their shit together. I hope 37 Signals decides to not treat theirs as an afterthought and brings it up to snuff with the excellence of their other offerings.

Shameless plug

Want to learn some more Python? Check out my screencasts at Mijingo.