california app design company

OAuth2 with Django REST Framework

January 20, 2015

UPDATED November 25, 2015

This post is part 2 of a series on using OAuth with Django REST Framework. Part 1 is the series overview and I share some of my thoughts on the process. Part 3 is about social auth (e.g., Facebook, Twitter authentication) using DRF. Part 4 offers a richer explanation of server vs. client oauth flows, and part 5 is about integrating parts 2 & 3.

Here at Yeti we build our APIs with Django REST Framework and use the OAuth2 scheme using Django OAuth Toolkit. I'm no OAuth expert and it took me awhile to figure it all out — in fact, this is the second version of this post! Hopefully this blog post will save others some trouble; it will show you everything you need to set up OAuth2 for your own application using DRF.

This post is NOT about setting up authentication with third-party services like Facebook (see Part 3 instead). It is also not about creating an authentication service for apps other than your own to use.

Setup

I'm assuming you already have a User model and have DRF setup.

  1. pip install django-oauth-toolkit
  2. Add 'oauth2_provider' to installed apps
  3. Add DOT to your authentication classes for DRF in settings.py
    REST_FRAMEWORK = { 
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'oauth2_provider.ext.rest_framework.OAuth2Authentication', 
        ), 
    } 
  4. Add DOT to your urls.py
    urlpatterns = patterns('',
        url(r'^', include(router.urls)),
        url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
        url(r'^admin/', include(admin.site.urls)),
    )
  5. python manage.py migrate

How all this application and client stuff is working

Setting up OAuth for your own application can be confusing. For me, I was used to oauth flows with third-party services like Facebook and Twitter, but it took me a bit to wrap my head around how that would work for my app and our users. It doesn't help that oauth can be implemented in different ways (1.0 vs 2.0, different grant types, etc.). 

When you use a third-party service

When you do your own OAuth

Essentially, the way OAuth works, there needs to be an application that a user is authenticating with. But now our app is the service providing authentication, so what is the appilcation supposed to be? Nothing really. It's just a dummy application that we set up. You can think of it as representing your front-end application if that helps.

Creating an application

In your Django admin or shell, you can create a new oauth2_provider.models.Application. Set client type to "public" and grant type to "resource owner password based". The name can just be whatever makes sense to you (e.g., "iOS App", "JS Frontend"), and the application should be owned by your admin user.

Note: The Django OAuth Toolkit tutorial says to make the client type "confidential". You can if you want, but since this app is actually going to be living on a public, insecure client (browser, mobile device), it makes more sense for the client type to be "public".

Example Sign Up

You don't have to use this, you're welcome to just make a user in the admin, but here's a full example:

serializers.py

from rest_framework import serializers

class SignUpSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('username', 'password')
        write_only_fields = ('password',)

views.py

from rest_framework import generics
from permissions import IsAuthenticatedOrCreate

class SignUp(generics.CreateAPIView):
    queryset = User.objects.all()
    serializer_class = SignUpSerializer
    permission_classes = (IsAuthenticatedOrCreate,)

permissions.py

from rest_framework import permissions

class IsAuthenticatedOrCreate(permissions.IsAuthenticated):
    def has_permission(self, request, view):
        if request.method == 'POST':
            return True
        return super(IsAuthenticatedOrCreate, self).has_permission(request, view)

urls.py

urlpatterns = patterns('',
    url(r'^sign_up/$', views.SignUp.as_view(), name="sign_up"),
)

Request / Response (in JSON)

request:

{
    "username": "django.pony",
    "password": "djangsta"
}

response:

{
    "username": "django.pony"
}

Get your token

The DOT docs say that you can get your token with the following request:

curl -X POST -d "grant_type=password&username=<user_name>&password=<password>" http://<client_id>:<client_secret>@localhost:8000/o/token/

Let's translate that.

  • Your request must be x-www-form-urlencoded. Having a different Content-Type header is not going to make DOT happy.
  • You can pass all the arguments (client_id, client_secret, grant_type, username, password) in that encoded form. E.g., though the curl request shows client_id and client_secret being passed in the url itself ahead of localhost, this is not a thing you need to try to figure out.
  • We are NOT actually using the client secret. If you are following along with this tutorial, and you made your Application public, you do not need to pass this value. If you were following the DOT tutorial instead and made your Application private, you will need to pass the client secret.

The mechanics of making this request with your Angular / iOS / whatever client are beyond the scope of this blog post, but I use a tool called Postman when playing around with APIs and the request looks like this:

You should get a response that looks like this:

{
    "access_token": "THIS IS THE IMPORTANT PART",
    "token_type": "Bearer",
    "expires_in": 36000,
    "refresh_token": "xyz456",
    "scope": "read write groups"
}

Using your token

From here out, using your token is straightforward. To make an authenticated request, just pass the Authorization header in your requests. It's value will be "Bearer YOUR_ACCESS_TOKEN".

Logging in

The last missing piece of this is handling login. If your token expires or a user logs out of your application, you'll need to get a new token. Logging in works the exact same as the last request to /o/token/: you pass client id, grant type, username, and password, and you'll get a fresh access token back.

Logging out

Remember, the only thing that lets the server know that your subsequent requests are authorized is that token you are passing along. To log out (or make an unauthorized request), just delete the access token on your front end.

In summary

These are the endpoints we just set up:

Endpoint Auth Request Response
sign_up/ username, password username
o/token/ username, password, client_id token (and some other stuff)
everything else OAuth2 (formatted as: Bearer YOUR_TOKEN)

You should now be able to sign up, then hit the o/token/ endpoint, and then be on your merry way.

is a Yeti Alum. At Yeti, she builds server-side APIs and web and mobile front-ends for applications. She is a former management consultant who decided to leave it all and attend a programming bootcamp. Outside of work, Baylee serves on the Board of Directors of Out for Undergrad, a non-profit that helps high-achieving LGBT undergrads reach their full potentials in their careers. Follow Baylee on Twitter.

blog comments powered by Disqus
OAuth2 with Django REST Framework https://s3-us-west-1.amazonaws.com/yeti-site-media/uploads/blog/.thumbnails/img_1135_cover.jpg/img_1135_cover-360x0.jpg
Yeti (415) 766-4198 https://s3-us-west-1.amazonaws.com/yeti-site-static/img/yeti-head-blue.png