integrate OAuth2 into Django

While pip recognizes hundreds of OAuth 2 packages, only a few perform what they’re meant to do. So, after having user authentication in place. You now want your users to be able to log in using Twitter, Facebook, or Google. It’s no problem. You’re just a few lines of code away from accomplishing your goal. This tutorial will show you Python Social Auth to incorporate OAuth 2 into your Django or Django Rest Framework.

The good news is that OAuth 2 has evolved as the industry standard for social and third-party authentication (used by services like Facebook, Google, and others), so you can concentrate on learning and implementing it to support a wide range of social authentication providers.

Your instincts as a Python developer may bring you to pip, the Python Package Index’s (PyPA) recommended mechanism for installing Python packages. The bad news is that pip is aware of 278 OAuth packages; 53 expressly reference Django. It takes a week to investigate the choices, let alone starting the actual coding.

In this tutorial, you’ll learn how to use Python Social Auth to incorporate OAuth 2 into your Django or Django Rest Framework. Although this post focuses on the Django REST Framework, the information presented here may develop similar functionality in various backend frameworks.

The OAuth 2 process

OAuth 2 was created to be a web authentication protocol. However, it isn’t the same as if it were built as a network authentication protocol because it presumes you have HTML rendering and browser redirection capabilities.

It is a disadvantage for a JSON-based API, but there is a workaround for dealing with this.

As a result, you’ll go through the steps as if you were developing a standard server-side website.

OAuth 2 Server-side Flow

The first phase takes place entirely outside of the application flow. First, the project owner must register each OAuth 2 provider for which you require logins.

They supply the OAuth 2 provider with a callback URI during this registration, where your application will be ready to receive requests. In addition, they get a client key and a client secret in exchange. These tokens are used to confirm login requests throughout the authentication procedure.

Your server code is referred to as the client in the tokens. On the other hand, OAuth 2 provider is the host. Therefore, they aren’t intended for your API’s users.

The stepwise flow in the server:

  • Page presents a login button to the user
  • Button click redirects to the social auth server
  • OAuth2 provider validates request; redirects user to server-side callback url, including an authorization code
  • Server makes REST request to OAuth2 provider, exchanging authorization code for an access token
  • Backend makes calls to OAuth2 provider as necessary

When your application generates a page with a button like “Log in with Facebook” or “Sign in with Google+,” the flow begins. In essence, these are nothing more than simple links, each of which goes to a URL similar to this:

https://oauth2provider.com/auth?
    response_type=code&
    client_id=CLIENT_KEY&
    redirect_uri=CALLBACK_URI&
    scope=profile&
    scope=email

Your client key and redirect URI have been submitted, but no secrets have been shared. In exchange, you’ve requested an authentication code and access to both the ‘profile’ and ’email’ scopes from the server. These scopes specify the permissions you ask for from the user and limit the access token’s authorization.

Following receipt, the user’s browser is forwarded to a dynamic page controlled by the OAuth 2 provider. Before proceeding, the OAuth 2 provider double-checks that the callback URI and client key are the same. If they do, depending on the user’s session tokens, the flow briefly diverges.

The user will be prompted to log in if they are not already logged in to that service. Then, the user is provided with a window asking for permission to allow your program to log in after they’ve logged in.

If the user accepts, the OAuth 2 server forwards them to the callback URI you specified, with an authorization code included in the query parameters:

GET https://api.yourapp.com/oauth2/callback/?code=AUTH_CODE.

The authorization code is a one-time-use token that expires quickly; as soon as you receive it, your server should issue a new request to the OAuth 2 provider, including both the authorization code and your client secret:

POST https://oauth2provider.com/token/?
  grant_type=authorization_code&
  code=AUTH_CODE&
  redirect_uri=CALLBACK_URI&
  client_id=CLIENT_KEY&
  client_secret=CLIENT_SECRET

The objective of this authorization code is to authenticate the POST request above, but it must be routed through the user’s system owing to the nature of the flow. As a result, it is inherently risky.

The authorization code’s limitations (i.e., that it expires rapidly and can only be used once) are in place to reduce the risk of sending an authentication credential through an untrusted system.

The main component of the OAuth 2 server-side login process is this call, which is made directly from your server to the OAuth 2 provider’s server. Controlling the call ensures that it is TLS-secured, making it more resistant to wiretapping assaults.

The authorization code guarantees that the user gave explicit consent. The client secret, which is never visible to your users, ensures that this request isn’t the result of a virus or spyware intercepting the authorization code on the user’s system.

If everything checks out, the server will return an access token, which you can use to make calls to that provider while logged in as the user.

Your server then redirects the user’s browser to the landing page for users who have just logged in after receiving the access token from the server. It’s typical to save the access token in the user’s server-side session cache so that the server can call the specified social provider as needed.

The user should never be given access to the access token!

Google, for example, contains a refresh token that extends the duration of your access token, while Facebook has an endpoint where you can exchange short-lived access tokens for longer-lived ones. However, we don’t care about these intricacies in this article because we will not use this flow.

For a REST API, this flow is inconvenient. While you could have the frontend client create the initial login page and the backend supply a callback URL, you’ll ultimately hit a snag. Once you’ve got the access token, you want to send the visitor to the front-landing end’s page, but there’s no clear, RESTful way to do so.

Fortunately, there is another OAuth 2 route that works much better in this situation.

The OAuth 2 Flow on the Client Side

The frontend becomes accountable for the full OAuth 2 process in this scenario. It follows the same pattern as the server-side flow, with one significant difference: frontends run on machines that users control. Therefore the client’s secret cannot be handed to them. The approach is to remove that stage from the process entirely.

As with the server-side flow, the initial step is to register the application.

The project owner registers the program as a web application rather than a desktop application in this situation. The OAuth 2 provider will still offer a client key, but no client secret is guaranteed.

The URL looks a little different this time, though:

https://oauth2provider.com/auth?
   response_type=token&
   client_id=CLIENT_KEY&
   redirect_uri=CALLBACK_URI&
   scope=profile&
   scope=email

So, what about the URI for redirection?

It is any frontend address that is ready to handle the access token correctly.

The frontend may briefly run a server capable of receiving HTTP requests on the user’s device, depending on the OAuth 2 library in use; in such case, the redirect URL is of the type http://localhost:7862/callback/?token=TOKEN.

Because the OAuth 2 server sends an HTTP redirect once the user accepts, and the browser on the user’s device processes the redirect. This address is correctly parsed, providing the frontend access to the token.

Alternatively, the frontend might create an appropriate page on its own. In either case, the frontend is in charge of parsing the query parameters and processing the access token at this point.

Users, on the other hand, are looking for authenticated access to your API. All the backend needs to provide an endpoint where the frontend may swap an access token from a social provider for a token that grants access to your API. The frontend can use the token to call the OAuth 2 provider’s API directly from this point forward.

Given that supplying the access token to the frontend is intrinsically less secure than the server-side flow, why enable it at all?

A stricter separation between a backend REST API and a user-facing frontend is possible with the client-side flow. Nothing prevents you from specifying your backend server as the redirect URI; the result would be a hybrid flow.

The problem is that the server must then build an acceptable user-facing page before handing control back to the frontend.

In current projects, it’s typical to keep the frontend UI and backend, which completely distinct all business logic. They usually communicate via a well-defined JSON API.

However, the hybrid flow described above muddles that separation of interests by requiring the backend to serve a user-facing page and create some flow to return control to the frontend.

Allowing the frontend to manage the access token is a time-saving solution that keeps the issues separate. It slightly raises the chance of a hacked client, but it generally works effectively.

Client Flow

  • Frontend presents login button to the user
  • Button click redirects to the social auth server
  • Frontend starts up a short-lived web server that accepts requests at the callback url
  • OAuth2 provider validates the request, redirects the user to the frontend callback url, including an access token
  • Frontend makes REST request to your backend, exchanging provider access token for an access token that works on your API
  • Frontend makes calls to your API; backend calls OAuth2 provider as necessary

This flow may appear hard to the frontend developer, especially if the frontend team is expected to do everything themselves. Both Facebook and Google, on the other hand, provide frameworks that allow frontend developers to integrate login buttons that manage the entire process with minimal settings.

Recipe for token exchange on the backend

Don’t be fooled: this isn’t an easy job. The backend is relatively insulated from the OAuth 2 process in the client flow. However, you’ll want it to have at least some of the following features.

  • Send at least one request to the OAuth 2 provider, only to make sure the token provided by the frontend is legitimate and not some random string.
  • Return a valid token for your API when the token is valid. Otherwise, an informative error will be returned.
  • Create a User model for them and populate it correctly if this is a new user.
  • If a Person model exists for this user, match them by their email address to access the correct existing account rather than creating a new one for the social login.
  • Update the user’s profile information based on the information they’ve shared on the social networking site.

The good news is that adding all of this functionality to the backend is a lot easier than you might think. So here’s the secret to getting everything to function on the backend in just two dozen lines of code.

Because the Python Social Auth library (“PSA”) is required, you must include both social-auth-core and social-auth-app-Django in your requirements.txt.

You’ll also need to set up the library according to the instructions. For the sake of clarity, some exception handling is not included.

@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,
        )

There’s one more thing you need to do in your settings, and you’re done:

AUTHENTICATION_BACKENDS = (
  'social_core.backends.google.GoogleOAuth2',
  'social_core.backends.facebook.FacebookOAuth2',
  'django.contrib.auth.backends.ModelBackend',
)
for key in ['GOOGLE_OAUTH2_KEY',
            'GOOGLE_OAUTH2_SECRET',
            'FACEBOOK_KEY',
            'FACEBOOK_SECRET']:
    # Use exec instead of eval here because we're not just trying to evaluate a dynamic value here;
    # we're setting a module attribute whose name varies.
    exec("SOCIAL_AUTH_{key} = os.environ.get('{key}')".format(key=key))
SOCIAL_AUTH_PIPELINE = (
  'social_core.pipeline.social_auth.social_details',
  'social_core.pipeline.social_auth.social_uid',
  'social_core.pipeline.social_auth.auth_allowed',
  'social_core.pipeline.social_auth.social_user',
  'social_core.pipeline.user.get_username',
  'social_core.pipeline.social_auth.associate_by_email',
  'social_core.pipeline.user.create_user',
  'social_core.pipeline.social_auth.associate_user',
  'social_core.pipeline.social_auth.load_extra_data',
  'social_core.pipeline.user.user_details',
)

You’re done! Just add a mapping to this function in your urls.py file, and you’re done!

What’s the deal with that magic?

Python Social Auth is a fascinating and challenging piece of software. It works with most common Python web frameworks, including Django, Flask, Pyramid, CherryPy, and WebPy, and can manage authentication and access to any of several dozen social auth providers.

For the most part, the code above is a normal Django REST framework (DRF) function-based view. It waits for POST requests on the path you specify in urls.py, and if you send it a request in the format it expects, it returns a User object or None.

If you get a User object, it’s of the model type you set up somewhere else in your project, which may or may not have existed before. PSA took care of validating the token, determining whether or not there was a user match, creating a user if necessary, and updating user information from the social source.

The SOCIAL AUTH PIPELINE

It defines the exact details of how a user is translated from the social provider’s user to yours and related with current users. Of course, there’s a lot more to learn about how everything works. But that’s beyond the scope of this article.

The @psa() decorator on the view is the crucial piece of magic since it adds some members to the request object that is given into your view. Request to the backend is the one that interests us the most (to PSA, a backend is any social authentication provider).

Based on the backend argument to the view provided by the URL itself, the proper backend was picked for us and appended to the request object.

Once you have the backend object, you can use the do auth method to authenticate yourself against that provider using your access code. As a result, the whole SOCIAL AUTH PIPELINE from your config file gets activated.

If you extend the pipeline, it can accomplish some amazing things, but it already performs everything you need it to do with its default, built-in capability.

After that, it’s simple DRF code as usual: if you have a valid User object, you can quickly return an appropriate API token. It’s simple to generate an error if you don’t get a valid User object back.

One disadvantage of this method is that, while it is relatively easy to return errors if they occur, it is difficult to gain much insight into what went wrong. This is because PSA swallows any information the server would have returned regarding the problem.

However, it is like a well-designed authentication system that keeps errors hidden. If an application ever tells a user after a login attempt, “Invalid Password,” it’s the same as saying “Congratulations!” You guessed a correct username.”

Why not create your own?

In their API calls, every few social OAuth 2 providers need or return the same information in the same way. However, there are several exceptions and unique instances.

It only takes a few lines of setup in your settings files to add a new social provider after you’ve already set up a PSA. There is no need to change any code. PSA takes care of all of that so you can concentrate on your application.

How am I going to test it?

That’s an excellent question! Unittest.mock isn’t well-suited to mimicking API calls hidden deep within a library’s abstraction layer; even figuring out which path to mock would be time-consuming.

Instead, you utilize the superb Responses library to mimic the providers at the HTTP level because PSA is built on top of the Requests library.

Unfortunately, a thorough explanation of testing is beyond the scope of this post. However, the simulated context manager and the SocialAuthTests class are two functions worth noting.

Allow PSA to handle the heavy lifting. That is to say, the OAuth2 process is sophisticated and thorough, with a lot of inherent complexity. Fortunately, much of that complexity may be avoided by bringing in a library specialized in managing it as painlessly as possible.

Summary

Python Social Auth is excellent at this. In under 25 lines of code, we’ve presented a Django/DRF view that uses the client-side, implicit OAuth2 protocol to achieve smooth user creation and matching. That’s not bad at all.

After going through the article from top to the very end, you should properly set up OAuth2 so that your users can log in using Twitter, Facebook, or Google.

This tutorial has been comprehensive enough to cover using Python Social Auth to incorporate OAuth 2 into your Django or Django Rest Framework. In the examples we covered, we showcased how both a server-side flow and client-side OAuth2 flow work. We hope this has been informative, and we are looking forward to your feedback.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *