This post is part 3 of a series on using OAuth with Django REST Framework. Part 2 focused on setting up your own OAuth2 server for your application, part 4 offers a richer explanation of server vs. client oauth flows, and part 5 is about integrating parts 2 & 3. For a summary of my thoughts on the process, see the series overview.
This post is to help you set up Django REST Framework with Python Social Auth; the goal here is to allow users to sign up and sign in to your app using Facebook, Twitter, etc., using API endpoints that you manage with Django REST Framework. If you are using Tastypie instead of DRF, see our complemetary piece: Integrating Django, Tastypie & Python Social Auth.
I'm assuming that you already have a Django project set up using DRF and some standard variant on a Django User model. With that out of the way, you just need to do a simple pip install python-social-auth and follow the Django configuration instructions. Don't forget to migrate your database.
Your front-end application will pass up the following data in a request:
{ "provider": "facebook", // or twitter, instagram, etc. "access_token": "ABC123", // user's access_token assigned by provider "access_token_secret": "DEF456" // required by some providers, e.g. Twitter}
You'll need to add a route to your view:
urlpatterns = patterns('', url(r'^social_sign_up/$', views.SocialSignUp.as_view(), name="social_sign_up"),)
And the view above responds with your user instance (my serializer is just returning some basic user data).
This is the bulk of the interesting work. Python social auth is a great out of the box solution using Django's views and templates, but takes a little picking apart to get to work with your DRF API. Python social auth knows how to use Django's views and urls to go through a server-side oauth flow. But if we want to initialize that process within our DRF view, we'll have to manually call some of the functions python social auth provides.
from django.contrib.auth import get_user_modelfrom rest_framework import statusfrom rest_framework.response import Responsefrom social.apps.django_app import load_strategyfrom social.apps.django_app.utils import load_backendfrom social.backends.oauth import BaseOAuth1, BaseOAuth2from social.exceptions import AuthAlreadyAssociatedUser = get_user_model()class SocialSignUp(generics.CreateAPIView): queryset = User.objects.all() serializer_class = SocialSignUpSerializer # This permission is nothing special, see part 2 of this series to see its entirety permission_classes = (IsAuthenticatedOrCreate,) def create(self, request, *args, **kwargs): """ Override `create` instead of `perform_create` to access request request is necessary for `load_strategy` """ serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) provider = request.data['provider'] # If this request was made with an authenticated user, try to associate this social # account with it authed_user = request.user if not request.user.is_anonymous() else None # `strategy` is a python-social-auth concept referencing the Python framework to # be used (Django, Flask, etc.). By passing `request` to `load_strategy`, PSA # knows to use the Django strategy strategy = load_strategy(request) # Now we get the backend that corresponds to our user's social auth provider # e.g., Facebook, Twitter, etc. backend = load_backend(strategy=strategy, name=provider, redirect_uri=None) if isinstance(backend, BaseOAuth1): # Twitter, for example, uses OAuth1 and requires that you also pass # an `oauth_token_secret` with your authentication request token = { 'oauth_token': request.data['access_token'], 'oauth_token_secret': request.data['access_token_secret'], } elif isinstance(backend, BaseOAuth2): # We're using oauth's implicit grant type (usually used for web and mobile # applications), so all we have to pass here is an access_token token = request.data['access_token'] try: # if `authed_user` is None, python-social-auth will make a new user, # else this social account will be associated with the user you pass in user = backend.do_auth(token, user=authed_user) except AuthAlreadyAssociated: # You can't associate a social account with more than user return Response({"errors": "That social media account is already in use"}, status=status.HTTP_400_BAD_REQUEST) if user and user.is_active: # if the access token was set to an empty string, then save the access token # from the request auth_created = user.social_auth.get(provider=provider) if not auth_created.extra_data['access_token']: # Facebook for example will return the access_token in its response to you. # This access_token is then saved for your future use. However, others # e.g., Instagram do not respond with the access_token that you just # provided. We save it here so it can be used to make subsequent calls. auth_created.extra_data['access_token'] = token auth_created.save() # Set instance since we are not calling `serializer.save()` serializer.instance = user headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) else: return Response({"errors": "Error with social authentication"}, status=status.HTTP_400_BAD_REQUEST)
What's going on here? We're handling a few different cases:
Admittedly this view is a bit long and not the most OO way to handle the nuances of all the different social providers. Ideally you could write something similar to PSA's provider classes that you could plug in here. Currently this has been tested with Facebook, Twitter, and Instagram.
That's it! You should now be able to use your app to sign up or login users from their social accounts (both will use the same view).
If you're reading this and wondering how to get this access token that you are passing to your API, the answer is that your front-end application is doing that. For example, you have a JavaScript or mobile application that is using the Facebook SDK, or is manually going through the provider's OAuth flow. The mechanics of that are outside the scope of this post, but to be clear, all that should be happening in the front-end. When you look at the documentation for whatever provider you are using, you will want to follow the instructions for the implicit or client-side oauth flow. Yes, DRF and python-social-auth are working server-side, but getting the access token is happening client-side, so that's the flow you want to follow. Essentially, the entire auth flow happens client-side--python social auth is just helping us request information from the social providers and save associations to the user. More detail on those processes in the next post in this series.