Django Crafts (4 Part Series)
1 Django 2FA With Google Authenticator
2 Role-Based Access Control in Django
3 Implementing SSO In Your Django Project
4 Captcha & reCaptcha For Django
For some developers, SSO might still be new to hear on daily purpose, whereas this has been there for a while and we keep using it daily whatever device we use. Google is one of the greatest organization relying on SSO for most services. SSO is an important aspect when it comes to improvement of UX (User Experience). So now the question you should ask yourself is:
What is SSO?
Single sign-on (SSO) is an authentication method that allows users to log in to multiple applications and websites with one set of credentials.
SSO Case Study (Google)
Before saying a word here, understand that this is just a shallow implementation of the system, I will like this to be a way for you to understand this better and know how to implement something similar to your own usecase.
Pay attention to the direction of the arrows and the different services we are pointing to. Without further ado, LET’S CRAFT THIS!!!
Implementation
This tutorial will cover the implementation of SSO in Django using Google SSO. We will have a few libraries to depend on and the code will be found in a GitHub repo later.
- Project creation and dependencies installation
<span>mkdir</span> <span>django_sso</span><span>cd</span> <span>django_sso</span><span>python</span> <span>-</span><span>m</span> <span>venv</span> <span>.</span><span>venv</span><span>source</span> <span>.</span><span>vevn</span><span>/</span><span>bin</span><span>/</span><span>activate</span><span>pip</span> <span>install</span> <span>python</span><span>-</span><span>social</span><span>-</span><span>auth</span> <span>social</span><span>-</span><span>auth</span><span>-</span><span>app</span><span>-</span><span>django</span><span>django</span><span>-</span><span>admin</span> <span>startproject</span> <span>django_sso</span> <span>.</span><span>python</span> <span>manage</span><span>.</span><span>py</span> <span>startapp</span> <span>accounts</span><span>touch</span> <span>accounts</span><span>/</span><span>urls</span><span>.</span><span>py</span><span>mkdir</span> <span>django_sso</span> <span>cd</span> <span>django_sso</span> <span>python</span> <span>-</span><span>m</span> <span>venv</span> <span>.</span><span>venv</span> <span>source</span> <span>.</span><span>vevn</span><span>/</span><span>bin</span><span>/</span><span>activate</span> <span>pip</span> <span>install</span> <span>python</span><span>-</span><span>social</span><span>-</span><span>auth</span> <span>social</span><span>-</span><span>auth</span><span>-</span><span>app</span><span>-</span><span>django</span> <span>django</span><span>-</span><span>admin</span> <span>startproject</span> <span>django_sso</span> <span>.</span> <span>python</span> <span>manage</span><span>.</span><span>py</span> <span>startapp</span> <span>accounts</span> <span>touch</span> <span>accounts</span><span>/</span><span>urls</span><span>.</span><span>py</span>mkdir django_sso cd django_sso python -m venv .venv source .vevn/bin/activate pip install python-social-auth social-auth-app-django django-admin startproject django_sso . python manage.py startapp accounts touch accounts/urls.py
Enter fullscreen mode Exit fullscreen mode
- Configuration of settings
<span># django_sso/settings.py </span><span>INSTALLED_APPS</span> <span>=</span> <span>[</span><span>...</span><span>'</span><span>social_django</span><span>'</span><span>,</span><span>]</span><span>TEMPLATES</span> <span>=</span> <span>[</span><span>#... </span> <span>'</span><span>DIRS</span><span>'</span><span>:</span> <span>[</span><span>BASE_DIR</span> <span>/</span> <span>'</span><span>templates</span><span>'</span><span>,</span><span>],</span><span>#... </span><span>]</span><span>AUTHENTICATION_BACKENDS</span> <span>=</span> <span>(</span><span>'</span><span>social_core.backends.google.GoogleOAuth2</span><span>'</span><span>,</span><span>'</span><span>django.contrib.auth.backends.ModelBackend</span><span>'</span><span>,</span><span>)</span><span>SOCIAL_AUTH_GOOGLE_OAUTH2_KEY</span> <span>=</span> <span>'</span><span>your-client-id</span><span>'</span><span>SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET</span> <span>=</span> <span>'</span><span>your-client-secret</span><span>'</span><span>LOGIN_URL</span> <span>=</span> <span>'</span><span>login</span><span>'</span><span>LOGOUT_URL</span> <span>=</span> <span>'</span><span>logout</span><span>'</span><span>LOGIN_REDIRECT_URL</span> <span>=</span> <span>'</span><span>home</span><span>'</span><span>=</span><span># django_sso/settings.py </span><span>INSTALLED_APPS</span> <span>=</span> <span>[</span> <span>...</span> <span>'</span><span>social_django</span><span>'</span><span>,</span> <span>]</span> <span>TEMPLATES</span> <span>=</span> <span>[</span> <span>#... </span> <span>'</span><span>DIRS</span><span>'</span><span>:</span> <span>[</span> <span>BASE_DIR</span> <span>/</span> <span>'</span><span>templates</span><span>'</span><span>,</span> <span>],</span> <span>#... </span><span>]</span> <span>AUTHENTICATION_BACKENDS</span> <span>=</span> <span>(</span> <span>'</span><span>social_core.backends.google.GoogleOAuth2</span><span>'</span><span>,</span> <span>'</span><span>django.contrib.auth.backends.ModelBackend</span><span>'</span><span>,</span> <span>)</span> <span>SOCIAL_AUTH_GOOGLE_OAUTH2_KEY</span> <span>=</span> <span>'</span><span>your-client-id</span><span>'</span> <span>SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET</span> <span>=</span> <span>'</span><span>your-client-secret</span><span>'</span> <span>LOGIN_URL</span> <span>=</span> <span>'</span><span>login</span><span>'</span> <span>LOGOUT_URL</span> <span>=</span> <span>'</span><span>logout</span><span>'</span> <span>LOGIN_REDIRECT_URL</span> <span>=</span> <span>'</span><span>home</span><span>'</span><span>=</span># django_sso/settings.py INSTALLED_APPS = [ ... 'social_django', ] TEMPLATES = [ #... 'DIRS': [ BASE_DIR / 'templates', ], #... ] AUTHENTICATION_BACKENDS = ( 'social_core.backends.google.GoogleOAuth2', 'django.contrib.auth.backends.ModelBackend', ) SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'your-client-id' SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'your-client-secret' LOGIN_URL = 'login' LOGOUT_URL = 'logout' LOGIN_REDIRECT_URL = 'home'=
Enter fullscreen mode Exit fullscreen mode
- Views and URLs configurations
<span># accounts/urls.py </span><span>from</span> <span>django.urls</span> <span>import</span> <span>path</span><span>,</span> <span>include</span><span>from</span> <span>accounts.views</span> <span>import</span> <span>login_view</span><span>,</span> <span>logout_view</span><span>,</span> <span>home_view</span><span>,</span> <span>generate_sso_token</span><span>urlpatterns</span> <span>=</span> <span>[</span><span>path</span><span>(</span><span>'</span><span>auth/</span><span>'</span><span>,</span> <span>include</span><span>(</span><span>'</span><span>social_django.urls</span><span>'</span><span>,</span> <span>namespace</span><span>=</span><span>'</span><span>social</span><span>'</span><span>)),</span><span>path</span><span>(</span><span>'</span><span>login/</span><span>'</span><span>,</span> <span>login_view</span><span>,</span> <span>name</span><span>=</span><span>'</span><span>login</span><span>'</span><span>),</span><span>path</span><span>(</span><span>'</span><span>logout/</span><span>'</span><span>,</span> <span>logout_view</span><span>,</span> <span>name</span><span>=</span><span>'</span><span>logout</span><span>'</span><span>),</span><span>path</span><span>(</span><span>'</span><span>home/</span><span>'</span><span>,</span> <span>home_view</span><span>,</span> <span>name</span><span>=</span><span>'</span><span>home</span><span>'</span><span>),</span><span>path</span><span>(</span><span>'</span><span>generate-sso-token/</span><span>'</span><span>,</span> <span>generate_sso_token</span><span>,</span> <span>name</span><span>=</span><span>'</span><span>generate_sso_token</span><span>'</span><span>),</span><span>]</span><span># django_sso/urls.py </span><span>from</span> <span>django.contrib</span> <span>import</span> <span>admin</span><span>from</span> <span>django.urls</span> <span>import</span> <span>path</span><span>,</span> <span>include</span><span>urlpatterns</span> <span>=</span> <span>[</span><span>path</span><span>(</span><span>'</span><span>admin/</span><span>'</span><span>,</span> <span>admin</span><span>.</span><span>site</span><span>.</span><span>urls</span><span>),</span><span>path</span><span>(</span><span>''</span><span>,</span> <span>include</span><span>(</span><span>'</span><span>accounts.urls</span><span>'</span><span>)),</span><span>]</span><span># accounts/views.py </span><span>from</span> <span>django.shortcuts</span> <span>import</span> <span>render</span><span>,</span> <span>redirect</span><span>from</span> <span>django.contrib.auth</span> <span>import</span> <span>logout</span><span>from</span> <span>django.contrib.auth.decorators</span> <span>import</span> <span>login_required</span><span>from</span> <span>django.utils</span> <span>import</span> <span>timezone</span><span>from</span> <span>.models</span> <span>import</span> <span>SSOUserToken</span><span>import</span> <span>uuid</span><span>def</span> <span>login_view</span><span>(</span><span>request</span><span>):</span><span>if</span> <span>request</span><span>.</span><span>user</span><span>.</span><span>is_authenticated</span><span>:</span><span>return</span> <span>redirect</span><span>(</span><span>'</span><span>home</span><span>'</span><span>)</span><span>return</span> <span>render</span><span>(</span><span>request</span><span>,</span> <span>'</span><span>login.html</span><span>'</span><span>)</span><span>def</span> <span>logout_view</span><span>(</span><span>request</span><span>):</span><span>logout</span><span>(</span><span>request</span><span>)</span><span>return</span> <span>redirect</span><span>(</span><span>'</span><span>login</span><span>'</span><span>)</span><span>@login_required</span><span>def</span> <span>home_view</span><span>(</span><span>request</span><span>):</span><span>return</span> <span>render</span><span>(</span><span>request</span><span>,</span> <span>'</span><span>home.html</span><span>'</span><span>)</span><span>@login_required</span><span>def</span> <span>generate_sso_token</span><span>(</span><span>request</span><span>):</span><span>if</span> <span>request</span><span>.</span><span>method</span> <span>==</span> <span>'</span><span>POST</span><span>'</span><span>:</span><span>user</span> <span>=</span> <span>request</span><span>.</span><span>user</span><span>expires_at</span> <span>=</span> <span>timezone</span><span>.</span><span>now</span><span>()</span> <span>+</span> <span>timezone</span><span>.</span><span>timedelta</span><span>(</span><span>hours</span><span>=</span><span>1</span><span>)</span><span>token</span> <span>=</span> <span>str</span><span>(</span><span>uuid</span><span>.</span><span>uuid4</span><span>())</span><span>SSOUserToken</span><span>.</span><span>objects</span><span>.</span><span>create</span><span>(</span><span>user</span><span>=</span><span>user</span><span>,</span><span>token</span><span>=</span><span>token</span><span>,</span><span>expires_at</span><span>=</span><span>expires_at</span><span>)</span><span>return</span> <span>render</span><span>(</span><span>request</span><span>,</span> <span>'</span><span>sso_token.html</span><span>'</span><span>,</span> <span>{</span><span>'</span><span>token</span><span>'</span><span>:</span> <span>token</span><span>})</span><span>return</span> <span>redirect</span><span>(</span><span>'</span><span>home</span><span>'</span><span>)</span><span># accounts/urls.py </span><span>from</span> <span>django.urls</span> <span>import</span> <span>path</span><span>,</span> <span>include</span> <span>from</span> <span>accounts.views</span> <span>import</span> <span>login_view</span><span>,</span> <span>logout_view</span><span>,</span> <span>home_view</span><span>,</span> <span>generate_sso_token</span> <span>urlpatterns</span> <span>=</span> <span>[</span> <span>path</span><span>(</span><span>'</span><span>auth/</span><span>'</span><span>,</span> <span>include</span><span>(</span><span>'</span><span>social_django.urls</span><span>'</span><span>,</span> <span>namespace</span><span>=</span><span>'</span><span>social</span><span>'</span><span>)),</span> <span>path</span><span>(</span><span>'</span><span>login/</span><span>'</span><span>,</span> <span>login_view</span><span>,</span> <span>name</span><span>=</span><span>'</span><span>login</span><span>'</span><span>),</span> <span>path</span><span>(</span><span>'</span><span>logout/</span><span>'</span><span>,</span> <span>logout_view</span><span>,</span> <span>name</span><span>=</span><span>'</span><span>logout</span><span>'</span><span>),</span> <span>path</span><span>(</span><span>'</span><span>home/</span><span>'</span><span>,</span> <span>home_view</span><span>,</span> <span>name</span><span>=</span><span>'</span><span>home</span><span>'</span><span>),</span> <span>path</span><span>(</span><span>'</span><span>generate-sso-token/</span><span>'</span><span>,</span> <span>generate_sso_token</span><span>,</span> <span>name</span><span>=</span><span>'</span><span>generate_sso_token</span><span>'</span><span>),</span> <span>]</span> <span># django_sso/urls.py </span><span>from</span> <span>django.contrib</span> <span>import</span> <span>admin</span> <span>from</span> <span>django.urls</span> <span>import</span> <span>path</span><span>,</span> <span>include</span> <span>urlpatterns</span> <span>=</span> <span>[</span> <span>path</span><span>(</span><span>'</span><span>admin/</span><span>'</span><span>,</span> <span>admin</span><span>.</span><span>site</span><span>.</span><span>urls</span><span>),</span> <span>path</span><span>(</span><span>''</span><span>,</span> <span>include</span><span>(</span><span>'</span><span>accounts.urls</span><span>'</span><span>)),</span> <span>]</span> <span># accounts/views.py </span><span>from</span> <span>django.shortcuts</span> <span>import</span> <span>render</span><span>,</span> <span>redirect</span> <span>from</span> <span>django.contrib.auth</span> <span>import</span> <span>logout</span> <span>from</span> <span>django.contrib.auth.decorators</span> <span>import</span> <span>login_required</span> <span>from</span> <span>django.utils</span> <span>import</span> <span>timezone</span> <span>from</span> <span>.models</span> <span>import</span> <span>SSOUserToken</span> <span>import</span> <span>uuid</span> <span>def</span> <span>login_view</span><span>(</span><span>request</span><span>):</span> <span>if</span> <span>request</span><span>.</span><span>user</span><span>.</span><span>is_authenticated</span><span>:</span> <span>return</span> <span>redirect</span><span>(</span><span>'</span><span>home</span><span>'</span><span>)</span> <span>return</span> <span>render</span><span>(</span><span>request</span><span>,</span> <span>'</span><span>login.html</span><span>'</span><span>)</span> <span>def</span> <span>logout_view</span><span>(</span><span>request</span><span>):</span> <span>logout</span><span>(</span><span>request</span><span>)</span> <span>return</span> <span>redirect</span><span>(</span><span>'</span><span>login</span><span>'</span><span>)</span> <span>@login_required</span> <span>def</span> <span>home_view</span><span>(</span><span>request</span><span>):</span> <span>return</span> <span>render</span><span>(</span><span>request</span><span>,</span> <span>'</span><span>home.html</span><span>'</span><span>)</span> <span>@login_required</span> <span>def</span> <span>generate_sso_token</span><span>(</span><span>request</span><span>):</span> <span>if</span> <span>request</span><span>.</span><span>method</span> <span>==</span> <span>'</span><span>POST</span><span>'</span><span>:</span> <span>user</span> <span>=</span> <span>request</span><span>.</span><span>user</span> <span>expires_at</span> <span>=</span> <span>timezone</span><span>.</span><span>now</span><span>()</span> <span>+</span> <span>timezone</span><span>.</span><span>timedelta</span><span>(</span><span>hours</span><span>=</span><span>1</span><span>)</span> <span>token</span> <span>=</span> <span>str</span><span>(</span><span>uuid</span><span>.</span><span>uuid4</span><span>())</span> <span>SSOUserToken</span><span>.</span><span>objects</span><span>.</span><span>create</span><span>(</span> <span>user</span><span>=</span><span>user</span><span>,</span> <span>token</span><span>=</span><span>token</span><span>,</span> <span>expires_at</span><span>=</span><span>expires_at</span> <span>)</span> <span>return</span> <span>render</span><span>(</span><span>request</span><span>,</span> <span>'</span><span>sso_token.html</span><span>'</span><span>,</span> <span>{</span><span>'</span><span>token</span><span>'</span><span>:</span> <span>token</span><span>})</span> <span>return</span> <span>redirect</span><span>(</span><span>'</span><span>home</span><span>'</span><span>)</span># accounts/urls.py from django.urls import path, include from accounts.views import login_view, logout_view, home_view, generate_sso_token urlpatterns = [ path('auth/', include('social_django.urls', namespace='social')), path('login/', login_view, name='login'), path('logout/', logout_view, name='logout'), path('home/', home_view, name='home'), path('generate-sso-token/', generate_sso_token, name='generate_sso_token'), ] # django_sso/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('accounts.urls')), ] # accounts/views.py from django.shortcuts import render, redirect from django.contrib.auth import logout from django.contrib.auth.decorators import login_required from django.utils import timezone from .models import SSOUserToken import uuid def login_view(request): if request.user.is_authenticated: return redirect('home') return render(request, 'login.html') def logout_view(request): logout(request) return redirect('login') @login_required def home_view(request): return render(request, 'home.html') @login_required def generate_sso_token(request): if request.method == 'POST': user = request.user expires_at = timezone.now() + timezone.timedelta(hours=1) token = str(uuid.uuid4()) SSOUserToken.objects.create( user=user, token=token, expires_at=expires_at ) return render(request, 'sso_token.html', {'token': token}) return redirect('home')
Enter fullscreen mode Exit fullscreen mode
Let’s have a look at the HTML files now.
- HTML contents
<span><!-- templates/login.html --></span><span><!DOCTYPE html></span><span><html></span><span><head></span><span><title></span>Login<span></title></span><span></head></span><span><body></span><span><h1></span>Login<span></h1></span><span><a</span> <span>href=</span><span>"{% url 'social:begin' 'google-oauth2' %}"</span><span>></span>Login with Google<span></a></span><span></body></span><span></html></span><span><!-- templates/home.html --></span><span><!DOCTYPE html></span><span><html></span><span><head></span><span><title></span>Home<span></title></span><span></head></span><span><body></span><span><h1></span>Welcome, {{ user.username }}!<span></h1></span><span><a</span> <span>href=</span><span>"{% url 'generate_sso_token' %}"</span><span>></span>Generate SSO Token<span></a></span><span><a</span> <span>href=</span><span>"{% url 'logout' %}"</span><span>></span>Logout<span></a></span><span></body></span><span></html></span><span><!-- templates/sso_token.html --></span><span><!DOCTYPE html></span><span><html></span><span><head></span><span><title></span>SSO Token<span></title></span><span></head></span><span><body></span><span><h1></span>Your SSO Token<span></h1></span><span><p></span>{{ token }}<span></p></span><span><a</span> <span>href=</span><span>"{% url 'home' %}"</span><span>></span>Back to Home<span></a></span><span></body></span><span></html></span><span><!-- templates/login.html --></span> <span><!DOCTYPE html></span> <span><html></span> <span><head></span> <span><title></span>Login<span></title></span> <span></head></span> <span><body></span> <span><h1></span>Login<span></h1></span> <span><a</span> <span>href=</span><span>"{% url 'social:begin' 'google-oauth2' %}"</span><span>></span>Login with Google<span></a></span> <span></body></span> <span></html></span> <span><!-- templates/home.html --></span> <span><!DOCTYPE html></span> <span><html></span> <span><head></span> <span><title></span>Home<span></title></span> <span></head></span> <span><body></span> <span><h1></span>Welcome, {{ user.username }}!<span></h1></span> <span><a</span> <span>href=</span><span>"{% url 'generate_sso_token' %}"</span><span>></span>Generate SSO Token<span></a></span> <span><a</span> <span>href=</span><span>"{% url 'logout' %}"</span><span>></span>Logout<span></a></span> <span></body></span> <span></html></span> <span><!-- templates/sso_token.html --></span> <span><!DOCTYPE html></span> <span><html></span> <span><head></span> <span><title></span>SSO Token<span></title></span> <span></head></span> <span><body></span> <span><h1></span>Your SSO Token<span></h1></span> <span><p></span>{{ token }}<span></p></span> <span><a</span> <span>href=</span><span>"{% url 'home' %}"</span><span>></span>Back to Home<span></a></span> <span></body></span> <span></html></span><!-- templates/login.html --> <!DOCTYPE html> <html> <head> <title>Login</title> </head> <body> <h1>Login</h1> <a href="{% url 'social:begin' 'google-oauth2' %}">Login with Google</a> </body> </html> <!-- templates/home.html --> <!DOCTYPE html> <html> <head> <title>Home</title> </head> <body> <h1>Welcome, {{ user.username }}!</h1> <a href="{% url 'generate_sso_token' %}">Generate SSO Token</a> <a href="{% url 'logout' %}">Logout</a> </body> </html> <!-- templates/sso_token.html --> <!DOCTYPE html> <html> <head> <title>SSO Token</title> </head> <body> <h1>Your SSO Token</h1> <p>{{ token }}</p> <a href="{% url 'home' %}">Back to Home</a> </body> </html>
Enter fullscreen mode Exit fullscreen mode
Results
The result might not look same at the time you see this tutorial, the source code has been updates with some more styles.
Conclusion
I hope you enjoyed crafting Django SSO and understood how much impact it has on UX. Happy CRAFTing. Don’t forget to follow for more. Drop your thoughts in the comments section.
Django Crafts (4 Part Series)
1 Django 2FA With Google Authenticator
2 Role-Based Access Control in Django
3 Implementing SSO In Your Django Project
4 Captcha & reCaptcha For Django
暂无评论内容