Week 1 — Social OAuth with Django

Valentina Kania
println-mic
Published in
5 min readMar 7, 2018
Println Social Login

When building a product, we need to take into account how our users are going to register and log themselves in to our apps. While manual sign in is still sufficient, connecting our app to existing social media account is way faster and more convenient for our users. Especially, when your app is small, most people won’t bother creating a new account just to get inside your app. Hence, Social OAuth becomes more widely-adopted, and now implemented in our Cloud Printing Application, Println.

On my first week of Sprint 1, I got the task to learn about OAuth process and implementing Google OAuth to my application for Software Projects class, Println. Here is what I learned so far.

The Google Auth Flow

Basically, the Google authorization flow for web applications goes like this:

  1. Asking User’s Permission via Google Authorization Server
    After clicking on the Sign In with Google button for the first time, user will be redirected to a page where user will be asked to grant permission on their data, such as email, contacts, and basic profile info. For Println app, we will be asking all permissions needed to access Google OAuth2 API v2.
    Upon submission, Google Authorization Server will provide us an authorization code.
  2. Exchanging Authorization Code to Access Token
    The client-side app will send a POST request to Google with the authorization code in exchange for a short-lived access token and a refresh token, which we will use to access the Google API.
  3. Requesting data from Google API using provided Access Token
    After receiving access token, our application server will make a request to Google API. Google will return user information as defined in our scopes before in point 1.
  4. Doing Whatever We Need with the Data
    In this case, our user info will be used to create an account, if a user with associated email is not yet registered, or to log a user in, if the email is already registered as our user.

Implementing Social OAuth client can be troublesome, especially because different social media platforms have different rules and protocol for their OAuth clients. In Python, though, we can use the widely-used Social Auth library, which encapsulates the authentication process, and automatically create or log users in to our app, so we can focus on the next steps.

Python Social Auth

Python has a robust social auth library called Python Social Auth (now split to social-auth-core and other framework-based modules to improve scalability; Django: social-auth-app-django). It’s not only applicable for Google OAuth, in fact, they provide sign in/up modules for many social platforms like Facebook, Github, etc. (full list here)

Because I am using Django’s User object, I only need to implement an AuthSerializer and respected API View.

class AuthSerializer(serializers.Serializer):    access_token = serializers.CharField(        allow_blank=False,        trim_whitespace=True,    )

for the views/auth.py:

@api_view(http_method_names=['POST'])
@permission_classes([AllowAny])
@psa()
def exchange_token(request, backend):
serializer = SocialSerializer(data=request.data)

if serializer.is_valid(raise_exception=True):
# This is the key line of code: with the @psa() decorator above,
# it engages the PSA machinery to perform whatever social authentication
# steps are configured in your SOCIAL_AUTH_PIPELINE. At the end, it either
# hands you a populated User model of whatever type you've configured in
# your project, or None.
user = request.backend.do_auth(serializer.validated_data['access_token'])

if user:
# if using some other token back-end than DRF's built-in TokenAuthentication,
# you'll need to customize this to get an appropriate token object
token, _ = Token.objects.get_or_create(user=user)
return Response({'token': token.key})

else:
return Response(
{'errors': {'token': 'Invalid token'}},
status=status.HTTP_400_BAD_REQUEST,
)

(courtesy of Toptal, link below)

Testing

As a good TDD developer, I created (more like, copied-and-pasted from Github) some tests before implementing my functions to make sure that my code works with the expected cases.

On TDD, the tests are created first, along with the stubs (to avoid syntax errors), but we leave them all to fail. These unit tests define our acceptance criteria. That way, we are enforced to implement our code in a way to satisfy all the tests.

The tests are created to check if the code will work (i.e. does not crash) under these expected cases:

  • A user has not registered themselves
  • A user has registered their social media account before, and wants to log in
  • A user has registered, and at one point, deleted their account, so they should not be able to log in anymore
  • An invalid/expired access token is given
  • Access token is empty

I implemented them in my TestGoogleAuth class which inherited APITestCase class, and RequestsMocks class from responses as Context Manager. Why not just unittest mock? Or HTTPretty? As we are trying to mock an API Request with requests library, unittest mock is out of the question. This leaves us with HTTPretty, a complex library that mocks HTTP protocol and give custom responses. However, in this project, I am using responses library, which claims to be the appropriate mocker for requests library.

@contextmanager
def mocked(endpoint):
with responses.RequestsMock() as rsps:
rsps.add_callback(responses.GET, endpoint,
callback=respond_to,
content_type='application/json',
match_querystring=True,
)
yield rsps

(again, thanks to Toptal)

However, testing this API feature on Postman requires a real token that your social media provider would accept, which, for Google OAuth, can be obtained here. You can select scopes or APIs that you want to get access to, and exchange your authorization code with an access token, too!

Some Notes so I can be graded on my course…

Git

Our team has applied Git workflow as instructed. As this week’s task focuses on Register backlog, our team created a branch called ‘register’ based on ‘sit_uat’ branch. Each developer codes in a new file to minimize conflicts.

I also implemented the RED, GREEN, REFACTOR commit, with details as follows:

  • [RED] commit tag, used on commit regarding unit tests that fail because functions are not yet implemented
  • [GREEN] commit tag, used on commit regarding function and class implementation, with all unit tests pass (OK)
  • [REFACTOR] commit tag (not yet used), should be used on commit regarding refactoring, cleaning codes, and renaming.
  • No commit tag, used on other commit messages, such as updating settings, creating code skeletons, etc.

Cheers!

Valentina — 1506757264

P.S. Special thanks to this site which helped me a lot on building this feature: https://www.toptal.com/django/integrate-oauth-2-into-django-drf-back-end

--

--