DEV Community

Cover image for Django authentication via Google, Deezer, and Spotify
Nikita Kholin
Nikita Kholin

Posted on • Originally published at kholinlabs.com

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',
]
Enter fullscreen mode Exit fullscreen mode

After that, migrate your database by running

python3 manage.py migrate
Enter fullscreen mode Exit fullscreen mode

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

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

And SOCIAL_AUTH_URL_NAMESPACE to your project's settings.py again

SOCIAL_AUTH_URL_NAMESPACE = 'social'
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

Add Google to AUTHENTICATION_BACKENDS in project's settings.py

AUTHENTICATION_BACKENDS = (
    'social_core.backends.google.GooglePlusAuth',
)
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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

AUTHENTICATION_BACKENDS = (
    'social_core.backends.google.GooglePlusAuth',
    'social_core.backends.deezer.DeezerOAuth2',
)
Enter fullscreen mode Exit fullscreen mode

And again, a link to sign in via Deezer

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

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']
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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',
)
Enter fullscreen mode Exit fullscreen mode

And again, a link to sign in via Spotify

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

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']
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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',
]
Enter fullscreen mode Exit fullscreen mode

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

urlpatterns = [
    # ...
    path('', include('releases.urls')),
    # ...
]
Enter fullscreen mode Exit fullscreen mode

Define a /releases URL in releases/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('releases', views.index, name='index'),
]
Enter fullscreen mode Exit fullscreen mode

Create a view in releases/views.py

def index(request):
    return render(request, 'releases/index.html')
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

And checking current count of users there

from django.contrib.auth.models import User
User.objects.count()
Enter fullscreen mode Exit fullscreen mode

We can also investigate what is the difference between them

vars(User.objects.all())
Enter fullscreen mode Exit fullscreen mode

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',
)
Enter fullscreen mode Exit fullscreen mode

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',
)
Enter fullscreen mode Exit fullscreen mode

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')),
    # ...
]
Enter fullscreen mode Exit fullscreen mode

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 = '/'
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Showing only relevant links

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 %}
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this story I’ve covered:

  • how to use a social-app-django library
  • how to create apps, set custom scopes in Google, Deezer, and Spotify and of course integrate those services into your application
  • how to create a simple success page to show after the sign in
  • what is a pipeline in social-app-django library
  • and how to sign users out

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.

Top comments (1)

Collapse
 
zorig profile image
ZG

Hi, thanks for the awesome article. I am trying to use Facebook to my django app. So i finally managed to create a successful login and account created. Now there is a case that i should handle which is when facebook user does not have an email address. What should i do in this case?