python-social-auth and Django, I’ve found a very straightfoward way of adding authentication to apps designed for CMU students. Given that all Andrew accounts are now Google Apps @ CMU accounts, we can take advantage of the widely used Google OAuth2 libraries to authenticate users, but just restrict sign-ins to the
andrew.cmu.edu “hosted domain.”
Finally! I’ve alluded many times to blogging about some of the things I’m learning from my experiences rewriting [email protected]. After a summer of putting it off and half a semester more of dawdling, I’ve finally managed to lay some solid groundwork.
One of the features that we plan on adding to Print 2.0 is a web interface. For people familiar with the way Print works currently, you interact with the service entirely through email. This leads to a clunky user experience and a lot of weird bugs and edge cases. With a web interface, we hope to streamline the process of account management and printing documents.
With this in mind, we needed to decide on a user model. Ideally, we’d like this model to give us two things:
- It should be secure. People who don’t already have access to CMU printers shoudn’t be able to easy gain access by using our system.
- It should be familiar. We want to design Print 2.0 to be as close to an official-feeling CMU app as we can without stepping on the ever-present administrative toes.
Naturally, this leads to an obvious solution: have people sign in using the same https://login.cmu.edu site, which is used for email, Blackboard, AutoLab, and many other on-campus services. The trick, then, is to figure out how to do this.
My first thought was to “use Shibboleth.” I put this in quotes because I didn’t exactly know what that meant at the time. What I was looking for was a simple OAuth2 API (like what Google or Facebook provide) that I could much just drop into my app with no other configuration required on the deployment machine. Shibboleth does not offer this solution.
Luckily, Google’s OAuth2 API has a feature that allowed me to do exactly what I wanted. Thanks to a couple of resourceful members of the CMU Computer Science Facebook group (Carlos Diaz-Padron and Arthur Lee), I found out about a barely-documented parameter that you can pass in on the auth URL to restrict Google OAuth2 logins to a specific hosted domain. Since all CMU Andrew accounts are Google Apps accounts (as of Fall 2013), we can ask Google to restrict logins to the
'andrew.cmu.edu' hosted domain. From here on, the only trick is to get whatever client library you’re using to handle authentication to pass this parameter with a value of
'andrew.cmu.edu' along to Google. The rest of this post describes a bare-bones way of doing this using Django.
The following is a pretty long-winded explanation of my personal struggles with this. If you’d really just like to learn by example and by making your own mistakes, check out the code on GitHub. I also found the
python-social-auth examples very helpful. In the spirit of gitorial, the commits are structured logically to give you a sense of how the app came together as I wrote it. In fact, I’ve even made a gitorial for this repo!
Python Social Auth
First up: pick a client library. I’ve had success using
python-social-auth in other projects, and it’s generally a mature project, so it’s what I picked.
Next up: skim the docs. Given the enormous size of this project (it has solutions for tons of python frameworks and tons of authentication backends), the docs are pretty comprehensive. Unfortunately, they fell short in the one place where I needed to look.
One of the neat things about the python social auth library is that it can be easily extended to support additional authentications backends using simple object-oriented techniques. To make this happen, very few pieces of information are hardcoded; in particular, the names of the variables that allow for configuration are dynamically generated based on the available backends.
This page outlines how to pass in extra, optional arguments while constructing the redirect URI for a particular authentication backend. It hints at the names of the variables that need to be defined, but sadly these hints are wrong.
This is a correction to the documentation provided at the above link. Extra authentication arguments can be passed in using any of the following variables:
SOCIAL_AUTH_AUTH_EXTRA_ARGUMENTS(no, this is not a typo)
I’m not sure what the difference between using the second and third variables are, but all variables of the first form will allow you to scope the extra arguments to a particular backend (in case you’re using more than one in the same app).
So in my case, I added the following line to my
Phew. That’s a long variable name. But Jake, how did you come up with
GOOGLE_OAUTH2 for the backend name? There’s really no good answer to this, because I haven’t been able to find a web-readable list of all the names in one place. The way this name is generated is by taking the
name property of the particular backend you’re using (in my case,
name = 'google-oauth2', which I found by looking at the source), replacing all hyphens with underscores and making everything capital.
The Django App
Now you should plenty of background knowledge to make sense of the Django app on your own, but I’ll reiterate the main points here for completeness. It will be pretty choppy in most places if you’re not referring to my example app on GitHub as you read.
First, we’ll get all our URLs in place. For Django, all the URLs dealing with logging in are already defined; we just have to include them:
Then we can set up some simple URLs (a home page and a logout view), and accompanying views for these URLs that do exactly what you think they should.
In our template, if the user is logged in, we’ll show their AndrewID (
user.username), or else we’ll hardcode the link to the Google OAuth2 authentication backend.
Note: if you plan on using multiple forms of authentication (like Facebook or GitHub) in this app, be sure to check out the python social auth example for how they generate their home page such that no URLs need to be hard coded. For our purposes, though, if there’s only one URL it will be fine to just write it out.
That’s basically it. The rest is up to you to fill in. Building off this model, you should be able to make basically any Django app that you’d otherwise want to build.
One thing you’ll want to look into is defining a custom Auth User model. Custom Auth User models allow you to plug into Django’s default auth system to take advantage of all the features it has available for keeping track of your users. The Django docs on this topic are actually pretty good, though you’ll likely want to find an example to help you work through the specifics.
As always, let me know if you have any questions or if by following these steps something didn’t work for you. Because of how bare bones this app is, it’s likely that I glossed over some points for the sake of minimalism. Comment below with your feedback and feel free to email me or make GitHub issues with your questions.
Jake on the Web
If you cared enough to read that far, you should consider following me on GitHub or paying a visit to my homepage. If this post was about one of my open source projects, make sure to star it on GitHub! I love hearing what people think, so feel free to comment, open an issue, or send me an email.