This is the third article in a multipart series on implementing the Django authentication system. I will continue to use the same demo survey application, which I refer to as Djang Survey, to aid in the demonstration of the many features of Django authentication.
Each article of this series focusses on a subset of authentication features that can be implemented with Django. This third article focusses on how to integrate social authentication with the help of Python Social Auth. Specifically, I will be showing how to integrate user login via Google OAuth2.
Series Contents:
The code for this series can be found on GitHub.
In order to use Google's OAuth2 service for authenticating users I need to create an application in the Google developer console. After that I can create OAuth API credentials to use inside the Django Survey app in conjunction with Python Social Auth. This will be the focus for the section.
Now I hesitate to include many screenshots at all of this process because user interfaces like this tend to change quite frequently but, I have decided to do so anyway. In the event that the UIs change I'm hoping that my descriptions of the general process will be enough to help the reader navigate through the process.
After clicking create the client id and client secret are displayed which I copy and place in a safe place.
This section will cover installing and configuring the Python Social Auth package for Django which is pretty heavy on the configuration side. To begin I install Python Social Auth using pip inside my activated python virtual environment.
(venv) $ pip install social-auth-app-django
Then I update the project settings at django_survey/settings.py variables listed below:
Disclaimer! I am going to break off into a bit of a tagent about security and properly managing things like app passwords as well as API credentials but, I will keep it short and to the point ... NEVER PUT THESE IN SOURCE CONTROL!!!
A much better approach is to use a hidden .env file which holds these sensitive item as environment variables which can then be excluded from source control by including them in the projects .gitignore file. Tthere are various ways to use hidden environment variable files (aka .env) but, the way I like to do it is through the python-dotenv library which I pip install as shown below.
(venv) $ pip install python-dotenv
Next I make a .env file in the root of my project next to the manage.py file which contains the client ID and client secret from the Google developer console set up.
# .env
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY='my-google-oauth-client-id'
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'my-google-oath-secret'
Then in the settings.py module I import the load_dotenv function from the dotenv module installed previously. This load_dotenv function is passed the path to the .env file which it reads and injects the values into the environemnt accessible via the os module from the Python standard library.
Below is my complete django_survey/settings.py module.
# settings.py
import os
from dotenv import load_dotenv
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
load_dotenv(verbose=True, dotenv_path=os.path.join(BASE_DIR, '.env'))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'szn5kz6r*+*13h$^llg8l6!45p+9d_nz)d3w03fnn+guhtpm@9'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_extensions',
'survey.apps.SurveyConfig',
'widget_tweaks',
'guardian',
'social_django',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'social_django.middleware.SocialAuthExceptionMiddleware'
]
AUTHENTICATION_BACKENDS = [
'social_core.backends.google.GoogleOAuth2',
'django.contrib.auth.backends.ModelBackend',
'guardian.backends.ObjectPermissionBackend',
]
ROOT_URLCONF = 'django_survey.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'social_django.context_processors.backends',
'social_django.context_processors.login_redirect',
],
},
},
]
WSGI_APPLICATION = 'django_survey.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = '/static/'
# custom
LOGOUT_REDIRECT_URL='/login/'
LOGIN_REDIRECT_URL='/profile/'
LOGIN_URL='/login/'
LOGIN_ERROR_URL = '/login/'
# social auth
SOCIAL_AUTH_URL_NAMESPACE = 'social'
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = os.getenv('SOCIAL_AUTH_GOOGLE_OAUTH2_KEY')
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = os.getenv('SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET')
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.user.create_user',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
)
Following that I update the project's main django_surveny/urls.py URLConf to include the social_django urls like so.
# django_survey/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('survey.urls')),
path('', include('social_django.urls')),
]
And last but, not least, I run the newly introduced Python Social Auth migrations.
(venv) $ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, guardian, sessions, social_django, survey
Running migrations:
Applying social_django.0001_initial... OK
Applying social_django.0002_add_related_name... OK
Applying social_django.0003_alter_email_max_length... OK
Applying social_django.0004_auto_20160423_0400... OK
Applying social_django.0005_auto_20160727_2333... OK
Applying social_django.0006_partial... OK
Applying social_django.0007_code_timestamp... OK
Applying social_django.0008_partial_timestamp... OK
First things first I should add new login buttons for logging in with Google to the login page like so.
<!-- login.html -->
{% extends 'survey/base.html' %}
{% load widget_tweaks %}
{% block content %}
<section class="hero is-success is-fullheight">
<div class="hero-body">
<div class="container">
<h1 class="title has-text-centered">
Django Survey
</h1>
<div class="columns">
<div class="column is-offset-2 is-8">
<h2 class="subtitle">
Login
</h2>
<form action="{% url 'login' %}" method="POST">
{% csrf_token %}
<div class="field">
<label for="{{ form.username.id_for_label }}" class="label">
Username
</label>
<div class="control">
{{ form.username|add_class:"input" }}
</div>
<p class="help is-danger">{{ form.username.errors }}</p>
</div>
<div class="field">
<label for="{{ form.password.id_for_label }}" class="label">
Password
</label>
<div class="control">
{{ form.password|add_class:"input" }}
</div>
<p class="help is-danger">{{ form.password.errors }}</p>
</div>
<div class="field">
<div class="control">
<button class="button is-link">Submit</button>
</div>
</div>
</form>
<hr>
<label for="" class="label">Or login with Google</label>
<div class="field">
<div class="control">
<a href="{% url 'social:begin' 'google-oauth2' %}" class="button">
<span class="icon">
<i class=" fab fa-google-plus-g"></i>
</span>
<span>Google</span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{% endblock %}
This leaves the login page looking as follows.
And thats it.
Users can now click the Google login button which will redirect them to authenticate with Google which then redirects them back to their profile page in the Django Survey application upon successful authentication.
thecodinginterface.com earns commision from sales of linked products such as the books above. This enables providing continued free tutorials and content so, thank you for supporting the authors of these resources as well as thecodinginterface.com
In this article I have shown how to integrate the Python Social Auth package into a Django application to provide users a way to authenticate with Google's OAuth2. The Python Social Auth package offers backends for many popular social networking sites. The general approach varies very little amongst them so, readers should be able to use this tutorial along with the official Python Social Auth docs to use one of the others. In the next article I will be covering how to implement a email confirmation step during the registration process along with implementing password resets.
Thanks for joining along on this tour of some of the awesome authentication features that can be implemented in the Django web framework using Python. As always, don't be shy about commenting or critiquing below.