NextJS/Single Sign On(SSO)/Discord/

How to configure Discord SSO in Django Rest Framework with Nextjs?

Published on

How to configure Discord SSO in Django Rest Framework with Nextjs?

Introduction :

Single Sign On (SSO) gives your users convenient but secure access to all their web applications with a single set of credentials. This guide explains how we can configure Discord SSO(Social Login) in the Django Rest Framework backend with Nextjs.

Django (Django rest Framework):

Django REST framework (DRF) is a powerful and flexible toolkit for building Web APIs. Its main benefit is that it makes serialization much easier. Django REST framework is based on Django's class-based views, so it's an excellent option if you're familiar with Django. To know more about relative links click here

Next js :

Next.js is a React framework. Next.js is constitutionally an excellent tool to achieve great SEO performance. building super fast static websites that behave dynamically. Next.js when building UI and UX there is flexibility. and the important great community support. To know more about relative links click here

NextAuth.js:

NextAuth. js is a completely secured authentication solution for implementing authentication in Next. js applications. It is a flexible authentication library designed to sync with any OAuth service, with full support for passwordless sign-in.To know more about relative links click here

Single sign-on(SSO):

Single sign-on (SSO) is a time-saving and highly secure user authentication process. SSO lets users access multiple applications with a single account and sign out instantly with one click.

How it Works:

how it works.png

 

Flow Diagram:



flow diagram.png

Steps:

Step 1: Signup / Login Discord

If you don't have a Discord account ( ), please proceed with the signup option. otherwise, log in with your existing credentials.

Step 2: Create an Application in Discord

  • In the Discord developer dashboard go to Applications and click New Application


  • It prompts a popup, In that popup Fill your application name and click Create

    Screenshot_11.png

Step 3: Retrieve the Discord Client Information

  • Then, click OAuth2 > General, In the Client information section, find the CLIENT ID and CLIENT SECRET. Copy them and paste them into a local text file. These keys will be used to interact with Discord.

    Screenshot_13.png

  • Then, click Add Redirect button and add redirect URI’s


add oauth.png

Step 4: Django Backend Creation

  • Create a Django project named discord_sso using the following command.

django-admin startproject discord_sso
  • Navigate to the project directory which you have created in the above step and create an app named accounts using the following command.

cd discord_sso python manage.py startapp accounts
  • The project structure is given below



1: Install djangorestframework:

In this blog, we are using the Django Rest framework Python library to create APIs, therefore please install the same to proceed further.

pip install djangorestframework

2: Install django-cors-headers:

django-cors-headers library needs to establish a connection from the React frontend to Django API, hence please install the same using the below command.

pip install django-cors-headers
  • To Install all required python packages for this project by executing the following command.

    pip install -r requirements.txt

A sample requirements.txt file can be found in the following git repository file.

https://github.com/episyche/discord_sso_django_nextjs_example/blob/main/backend/discord_sso/requirements.txt

  • Update the django settings.py file with the following items.

    • In INSTALLED_APPS section add your app name (i.e. accounts) , rest_framework and rest_framework.authtoken

      #settings.py INSTALLED_APPS = [ ..... 'accounts', 'rest_framework',     'rest_framework.authtoken', ]
    • also add the following piece of code:

      CORS_ORIGIN_ALLOW_ALL = True AUTH_USER_MODEL = 'accounts.User'

       

      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', 'corsheaders.middleware.CorsMiddleware', # <<< newly added line ]
installed app.png

 

A sample settings.py file can be found in the following git repository file.

https://github.com/episyche/discord_sso_django_nextjs_example/blob/main/backend/discord_sso/discord_sso/settings.py

  • In your project, go to your /accounts directory and create a file called serializers.py



image-20220824-121333.png
  • In /accounts/models.py, add User model.

from django.db import models from django.utils import timezone from django.contrib.auth.models import AbstractUser class User(AbstractUser): email = models.EmailField(unique=False) username = models.CharField(max_length=30, unique=True) first_name = models.CharField(max_length=30, blank=True) last_name = models.CharField(max_length=50, blank=True) date_joined = models.DateTimeField(auto_now_add=True) is_active = models.BooleanField(default=False) is_deleted = models.BooleanField(default=False) created_date = models.DateTimeField(default=timezone.now) modified_date = models.DateTimeField(auto_now=True) created_by = models.DateTimeField(default=timezone.now) modified_by = models.DateTimeField(auto_now=True) auth_provider = models.CharField(max_length=50, blank=True, default='email')
  • In /accounts/serializers.py, add the following code.

from rest_framework import serializers from lib.social_account import discord from lib.register.register import register_social_user class DiscordAuthSerializer(serializers.Serializer): """Handles serialization of discord related data""" auth_token = serializers.CharField() def validate_auth_token(self, auth_token): user_data = discord.Discord.validate(auth_token) try: email = user_data['email'] provider = 'discord' except: raise serializers.ValidationError( 'The token is invalid or expired. Please login again.' ) return register_social_user( provider=provider, user_id=None, email=email, name=None)

Sample code for the /accounts/serializers.py can be found in the following Github URL.

https://github.com/episyche/discord_sso_django_nextjs_example/blob/main/backend/discord_sso/accounts/serializer.py

  • Create the lib directory in the following hierarchy.

    lib → social_account

    lib → register.

  • After that, please create a discord.py file inside the lib/social_account directory


Screenshot_2.png
  • In lib/social_account/discord.py, add the following snippet.

import requests class Discord: """ Discord class to fetch the user info and return it """ @staticmethod def validate(auth_token): """ validate method Queries the discord url to fetch the user info """ try: access_token = auth_token headers = {'Authorization': 'Bearer %s' %access_token } resp = requests.get('https://discord.com/api/v6/users/@me' , headers=headers) user_info = resp.json() return user_info except: return "The token is either invalid or has expired""

Sample code for the lib/social_account/discord.py file can be found in the following Github URL.

https://github.com/episyche/discord_sso_django_nextjs_example/blob/main/backend/discord_sso/lib/social_account/discord.py

  • Navigate to the lib/register directory and create a file called register.py


Screenshot_3.png
  • In lib/register/register.py, add the following code.

from rest_framework.authtoken.models import Token from accounts.models import User from django.conf import settings from rest_framework.exceptions import AuthenticationFailed def register_social_user(provider, user_id, email, name): filtered_user_by_email = User.objects.filter(email=email) if filtered_user_by_email.exists(): if provider == filtered_user_by_email[0].auth_provider: new_user = User.objects.get(email=email) registered_user = User.objects.get(email=email) registered_user.check_password(settings.SOCIAL_SECRET) Token.objects.filter(user=registered_user).delete() Token.objects.create(user=registered_user) new_token = list(Token.objects.filter( user_id=registered_user).values("key")) return { 'username': registered_user.username, 'email': registered_user.email, 'token': str(new_token[0]['key'])} else: raise AuthenticationFailed( detail='Please continue your login using ' + filtered_user_by_email[0].auth_provider) else: user = { 'username': email, 'email': email, 'password': settings.SOCIAL_SECRET } user = User.objects.create_user(**user) user.is_active = True user.auth_provider = provider user.save() new_user = User.objects.get(email=email) new_user.check_password(settings.SOCIAL_SECRET) Token.objects.create(user=new_user) new_token = list(Token.objects.filter(user_id=new_user).values("key")) return { 'email': new_user.email, 'username': new_user.username, 'token': str(new_token[0]['key']), }

Sample code for the lib/register/register.py file can be found in the following Github URL.

https://github.com/episyche/discord_sso_django_nextjs_example/blob/main/backend/discord_sso/lib/register/register.py

  • Navigate to accounts django app directory, and create a DiscordSocialAuthView class in views.py.

from django.shortcuts import render from accounts.serializer import * from rest_framework import status from rest_framework.response import Response from rest_framework.generics import GenericAPIView from rest_framework.decorators import permission_classes from rest_framework.permissions import AllowAny @permission_classes((AllowAny, )) class DiscordSocialAuthView(GenericAPIView): serializer_class = DiscordAuthSerializer def post(self, request): """ POST with "auth_token" Send an accesstoken as from discord to get user information """ serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) data = ((serializer.validated_data)['auth_token']) return Response(data, status=status.HTTP_200_OK)

Sample code for the /accounts/views.py can be found in the following Github URL.

https://github.com/episyche/discord_sso_django_nextjs_example/blob/main/backend/discord_sso/accounts/views.py

  • After creating the class add the urls.py file under your accounts django app.


Screenshot_4.png
  • In your core folder navigate to urls.py(core folder) and add your path with <appname> and <url_filename>(i.e. created in the above step). Your core urls.py settings are like the below.

from django.contrib import admin from django.urls import path , include urlpatterns = [ path('admin/', admin.site.urls), path('accounts/', include('<your_app_name>.<url_filename>')), ]

For example

from django.contrib import admin from django.urls import path , include urlpatterns = [ path('admin/', admin.site.urls), path('accounts/', include('accounts.urls')), ]
Screenshot_77.png


A sample urls.py(core folder) file can be found in the following git repository file.

https://github.com/episyche/discord_sso_django_nextjs_example/blob/main/backend/discord_sso/discord_sso/urls.py

  • Now, navigate to the Django app directory (i.e accounts ) and add the DiscordSocialAuthView Class API path in urls.py

from .views import* from django.urls import path urlpatterns = [ path('discord/', DiscordSocialAuthView.as_view()), ]
Screenshot_66.png


A sample urls.py(accounts) file can be found in the following git repository file.

https://github.com/episyche/discord_sso_django_nextjs_example/blob/main/backend/discord_sso/accounts/urls.py

  • Before run the django project . Run the following commands to migrate the models

python manage.py makemigrations python manage.py migrate
  • To run the Django project . Run the following command

    python manage.py runserver
  • Test the API using postman or any other API Client tool.

postman.png

 

Step 5: Next.js Frontend Creation

Install Nodejs

  • Before we create Next App we need to install node.js.

  • If you already installed node.js please proceed to next step

Create a new Next.js app for the Single sign-on process “frontend”

After installing Node.js open the terminal or command prompt and create the next.js app with the following commands

npx create-next-app frontend

Configure the Next.js project to establish a connection with Github

After creating the Nextjs app then install next-auth using the following command.

npm i next-auth
  • In your project, go to your /pages/api/ directory and create a directory auth . under auth directory create file called [...nextauth].js. This means that all of the routes starting with /api/auth will be handled by the [...nextauth].js file.



project structure frontend.png
  • In /pages/api/auth/[...nextauth].js, write the following piece of code:

  • Also update clientId and clientSecret with your credentials

import NextAuth from "next-auth" import DiscordProvider from "next-auth/providers/discord"; var userCredential = [] export const authOptions = { providers: [ DiscordProvider({ clientId: "<YOUR_CLIENT_ID>", clientSecret: "<YOUR_CLIENT_SECRET>", }) ], callbacks: { async jwt(token, user, account, profile, isNewUser) { var condition = token.account?.provider if (condition === "discord") { userCredential = { auth_token: token.account.access_token } userCredential['provider']='discord' } }, async session({ session, token, user }) { return userCredential } }, } export default NextAuth(authOptions)

Sample code for the /pages/api/[...nextauth].js file can be found in the following Github URL.

https://github.com/episyche/discord_sso_django_nextjs_example/blob/main/frontend/discord_sso_f/pages/api/auth/%5B...nextauth%5D.js

  • To make use of Next-auth in the app, please update the pages/_app.js file with the below snippet.

import { SessionProvider } from "next-auth/react" import "../styles/globals.css" export default function App({ Component, pageProps: { session, ...pageProps }, }) { return ( <SessionProvider session={session}> <Component {...pageProps} /> </SessionProvider> ) }

Sample code for the pages/_app.js file can be found in the following Github URL.

https://github.com/episyche/discord_sso_django_nextjs_example/blob/main/frontend/discord_sso_f/pages/_app.js

With Next-auth added to our application, we can now build our sign-in page. First, we will set up our page to display the Sign in to Discordbutton if the user is not authenticated, else it returns our application. To do this, we modify the index.js file as shown below:

import React, { useEffect } from "react"; import { useSession, signIn } from "next-auth/react" export default function Home({ providers }) { const { data: session, loading } = useSession() useEffect(() => { if (session) { if (session.provider === "discord") { var auth_token = session.auth_token backendapi(auth_token) } } }, [session]) function backendapi(auth_token) { fetch(`http://127.0.0.1:8000/accounts/discord/`, { method: "post", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ "auth_token": auth_token }), }).then((data) => data.json()) .then((res) => { if (res.token) { document.getElementById("email_id").innerText = res.email document.getElementById("token").innerText = res.token } }) } return ( <> <div id='discord-container'> <div type="submit" id='discordlogin' onClick={() => signIn("discord")}> <div className='discord-button'> <h1> <img className="discord-img" src="https://cdn3.iconfinder.com/data/icons/popular-services-brands-vol-2/512/discord-512.png" ></img> </h1> <button className="discord-login-btn">Sign in to Discord</button> </div> </div> </div> <div className='new_text'> <div > <label>Email Id : </label> <label id='email_id'></label> </div> <div > <label>Auth token : </label> <label id='token'></label> </div> </div> </> ) }

Sample code for the index.js file can be found in the following Github URL.

https://github.com/episyche/discord_sso_django_nextjs_example/blob/main/frontend/discord_sso_f/pages/index.js

You can run your app via CLI with the following command and view it in your browser:

npm run dev

Result:

  • After successfully running both the backend and frontend apps. If you open the Nextjs URL (i.e localhost:3000) in your browser the following page will appear.


result.png
  • Click Sign in to Discord button , it will redirect to discord authorize page . click Authorize



authorize.png
  • If authorization is success, then you will get the email and token .



result2.png

Comments