Django authentication via Google, Deezer, and Spotify

In this tutorial we will add an ability to sign up, sign in via Google, Deezer, and Spotify, view a success screen, and sign out afterward. Our project is going to be about music releases notifications.

I’ve already created a Django project with a landing page in a previous tutorial.

How will we do that?

We’re going to use a social-app-django library. It supports a huge number of auth providers. They are also called “Backends” in the library. As you could guess, we will need Google, Deezer, and Spotify.

To install the library please follow an official installation guide.

Adding social-app-django to your Django project

First of all, you need to add the library to our INSTALLED_APPS in your project’s settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # ...
    'social_django',
    # ...
    'pages',
]

After that, migrate your database by running

python3 manage.py migrate

Then add social_django.urls to your project’s urls.py

urlpatterns = [
    # ...
    path('social/', include('social_django.urls')),
    # ...
]

And SOCIAL_AUTH_URL_NAMESPACE to your project’s settings.py again

SOCIAL_AUTH_URL_NAMESPACE = 'social'

Now you should have nothing, but we’re ready to add an auth provider to the project. For more information, you can read the official python-social-auth installation guide for Django.

Authentication via Google(+)

First, you need to get credentials for the application

  1. Visit Google API Console
  2. Go to credentials
  3. Create credentials
  4. Add http://127.0.0.1:8000/social/complete/google-plus/ to Authorized redirect URIs
  5. Here you’ll have to add your production redirect URL once you have one

Now you should see Client ID and Client secret in your app. Add them to project’s settings.py

SOCIAL_AUTH_GOOGLE_PLUS_KEY = 'Your Client ID'
SOCIAL_AUTH_GOOGLE_PLUS_SECRET = 'Your Client secret'

Add Google to AUTHENTICATION_BACKENDS in project’s settings.py

AUTHENTICATION_BACKENDS = (
    'social_core.backends.google.GooglePlusAuth',
)

And finally, add a link to sign in via Google to your menu or header or wherever you want your users to click to sign in


<a href="{% url "social:begin" "google-plus" %}">Google</a>

For more reading about how authentication via Google works here’s the official guide.

Authentication via Deezer

The process for Deezer authentication is pretty similar to Google’s one. First, getting credentials

  1. Visit Deezer for developers
  2. Create an app
  3. Set http://127.0.0.1:8000/social/complete/deezer/ to Redirect URL after authentication
  4. And again, here you’ll have to add your production redirect URL once you have one

Now you should see Application id and a Secret Key in your app. Add them to project’s settings.py

SOCIAL_AUTH_DEEZER_KEY = 'Your Application id'
SOCIAL_AUTH_DEEZER_SECRET = 'Your Secret Key'

Again, add Deezer to AUTHENTICATION_BACKENDS in project’s settings.py

AUTHENTICATION_BACKENDS = (
    'social_core.backends.google.GooglePlusAuth',
    'social_core.backends.deezer.DeezerOAuth2',
)

And again, a link to sign in via Deezer


<a href="{% url "social:begin" "deezer" %}">Deezer</a>

One more thing

You might want to ask for some permissions. For example, Deezer does not provide user’s email by default. You need to ask a permission for email. You can check out a full list of available permissions in official permissions documentation.

Adding required permissions is pretty simple with the social-app-django library. You just need to add permission you want to a scope constant in the project’s settings.py. The constant, in Deezer case, has to be called SOCIAL_AUTH_DEEZER_SCOPE

SOCIAL_AUTH_DEEZER_SCOPE = ['basic_access', 'email']

Authentication via Spotify

Spotify authentication is as similar to Google as Deezer is.

  1. Visit Spotify for developers
  2. Create a Client ID
  3. Set http://127.0.0.1:8000/social/complete/spotify/ to Redirect URIs
  4. And again, here you’ll have to add your production redirect URL once you have one

Now you should see Client ID and a Client Secret in your app. Add them to project’s settings.py

SOCIAL_AUTH_SPOTIFY_KEY = 'Your Client ID'
SOCIAL_AUTH_SPOTIFY_SECRET = 'Your Client Secret'

Again, add Spotify to AUTHENTICATION_BACKENDS in project’s settings.py

AUTHENTICATION_BACKENDS = (
    'social_core.backends.google.GooglePlusAuth',
    'social_core.backends.deezer.DeezerOAuth2',
    'social_core.backends.spotify.SpotifyOAuth2',
)

And again, a link to sign in via Spotify


<a href="{% url "social:begin" "spotify" %}">Spotify</a>

Regarding permissions, I need user-read-email and user-library-read. You can check out a full list of available permissions in official permissions documentation.

SOCIAL_AUTH_SPOTIFY_SCOPE = ['user-read-email', 'user-library-read']

Life after sign in

Usually, after a sign in it’s a good idea to show some sort of dashboard or feed or whatever is the main purpose of your application. I’m going to show a screen with all the latest music releases.

First, let’s create a releases app

python3 manage.py startapp releases

Add the app to installed apps in the project’s settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'social_django',
    'pages',
    'releases',
]

Add URLs to the project’s URLs in the project’s urls.py

urlpatterns = [
	# ...
    path('', include('releases.urls')),
    # ...
]

Define a /releases URL in releases/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('releases', views.index, name='index'),
]

Create a view in releases/views.py

def index(request):
    return render(request, 'releases/index.html')

Create a simple template to show that we care about our users in releases/templates/releases/index.html (I hate to have to write releases two times too)

<h1>Latest Releases</h1>
<p>We care about you, but we don't have anything done yet.</p>

Now that we have somewhere to redirect a user after a sign in, there is only one thing left to do — set a LOGIN_REDIRECT_URL in the project’s settings.py

LOGIN_REDIRECT_URL = '/releases'

After this line is added all users should go directly to /releases URL after they sign in.

Fixing duplicate users

Now that we have the ability to sign in with a couple different accounts there is one problem — we don’t do anything with duplicate users, users that have identical email. We can quickly check that by signing up 2 times via two different services (with Google and Spotify for example).

Then we can check that there are 2 users by running a Django shell

python3 manage.py shell

And checking current count of users there

from django.contrib.auth.models import User
User.objects.count()

We can also investigate what is the difference between them

vars(User.objects.all())

To fix this problem we’ll have to learn a bit more how social-app-django works.

When a user signs in they go through a so-called pipeline. Here’s how the default pipeline looks like

(
    # Get the information we can about the user and return it in a simple
    # format to create the user instance later. On some cases the details are
    # already part of the auth response from the provider, but sometimes this
    # could hit a provider API.
    'social_core.pipeline.social_auth.social_details',

    # Get the social uid from whichever service we're authing thru. The uid is
    # the unique identifier of the given user in the provider.
    'social_core.pipeline.social_auth.social_uid',

    # Verifies that the current auth process is valid within the current
    # project, this is where emails and domains whitelists are applied (if
    # defined).
    'social_core.pipeline.social_auth.auth_allowed',

    # Checks if the current social-account is already associated in the site.
    'social_core.pipeline.social_auth.social_user',

    # Make up a username for this person, appends a random string at the end if
    # there's any collision.
    'social_core.pipeline.user.get_username',

    # Send a validation email to the user to verify its email address.
    # Disabled by default.
    # 'social_core.pipeline.mail.mail_validation',

    # Associates the current social details with another user account with
    # a similar email address. Disabled by default.
    # 'social_core.pipeline.social_auth.associate_by_email',

    # Create a user account if we haven't found one yet.
    'social_core.pipeline.user.create_user',

    # Create the record that associates the social account with the user.
    'social_core.pipeline.social_auth.associate_user',

    # Populate the extra_data field in the social record with the values
    # specified by settings (and the default ones like access_token, etc).
    'social_core.pipeline.social_auth.load_extra_data',

    # Update the user record with any changed info from the auth service.
    'social_core.pipeline.user.user_details',
)

If you like to, you can read a description for each stage of the pipeline. But what interests us at the moment is the 7th step, social_core.pipeline.social_auth.associate_by_email which is disabled by default. What it does is it

Associates the current social details with another user account with a similar email address

Sounds like exactly what we need. Just uncomment the line with this setting and copy the pipeline to your project’s settings.py

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.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 can read more about in the official docs for pipelines. We’ll build a custom pipeline in the next part of the series.

Signing out

You might want to add an ability for your users to sign out. I would not recommend doing that since this is just another opportunity for a user to leave your site but In case you still want to add it, it’s pretty easy.

Django has an already built-in support for authentication called django.contrib.auth. Right now, we’ll need just the sign-out part of it.

First, add django.contrib.auth.urls to your project’s urls.py

urlpatterns = [
    # ...
    path('', include('django.contrib.auth.urls')),
    # ...
]

Then, set LOGOUT_REDIRECT_URL to '/' in your project’s settings.py so that users are redirected to the homepage after they sign out

LOGOUT_REDIRECT_URL = '/'

Now, all that is left is to add a sign-out link wherever you want


<a href="{% url 'logout' %}" class="header-link">Log out</a>

But right now your user can see all sign in links as well as a sign-out link. You probably wouldn’t want a user to sign in or out 2 times and what does that even mean? So let’s show only relevant links.

Django’s user model has a special attribute for that called is_authenticated that we can use. We can just use a simple if in our template and check if a user, a variable which is already available in our templates, is_authenticated.


{% if user.is_authenticated %}
    <a href="/logout">Log out</a>
{% else %}
    <a href="{% url "social:begin" "google-plus" %}">Google</a>
    <a href="{% url "social:begin" "deezer" %}">Deezer</a>
    <a href="{% url "social:begin" "spotify" %}">Spotify</a>
{% endif %}

Conclusion

In this story I’ve covered:

This is the second part of the series of articles about the MuN. Stay tuned for part 3. You can find the code of this project, as well as my other projects, on my GitHub page. Leave your comments down below and follow me if you liked this article.