NextJS/Single Sign On(SSO)/Discord/
Published on
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 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 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 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) 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.
/Discord/125/content_image/1ebb80c3-392f-4825-8b91-4c996ac57be5.png)
/Discord/125/content_image/06e6a412-d0e9-420b-997d-41e6b46c1b15.png)
If you don't have a Discord account (Discord Developer Portal — API Docs for Bots and Developers ), please proceed with the signup option. otherwise, log in with your existing credentials.
In the Discord developer dashboard go to Applications and click New Application
/Discord/125/content_image/563e43c0-993a-4164-9d4e-256775ce7617.png)
It prompts a popup, In that popup Fill your application name and click Create
/Discord/125/content_image/81c32b37-11fd-49ed-b139-5e843ed60acf.png)
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.
/Discord/125/content_image/14b6d6f3-9627-415e-b095-758b372bb44c.png)
Then, click Add Redirect button and add redirect URI’s
/Discord/125/content_image/9ebe820c-5919-4892-b865-382ebf5565bd.png)
Create a Django project named discord_sso using the following command.
django-admin startproject discord_ssoNavigate 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 accountsThe project structure is given below
/Discord/125/content_image/e74c3377-c763-4cc5-a2f6-c7bb8824a49f.png)
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 djangorestframeworkdjango-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-headersTo Install all required python packages for this project by executing the following command.
pip install -r requirements.txtA sample requirements.txt file can be found in the following git repository file.
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
] /Discord/125/content_image/28e93dd4-d8e8-418c-8e93-68c46d5b86fa.png)
A sample settings.py file can be found in the following git repository file.
In your project, go to your /accounts directory and create a file called serializers.py
/Discord/125/content_image/ada90c1b-4dd0-4f49-a03b-3e20b8b91e41.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.
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
/Discord/125/content_image/2327ac01-08c4-454d-972e-c56e7458179b.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.
Navigate to the lib/register directory and create a file called register.py
/Discord/125/content_image/4cfa0b07-99bb-4746-9e2f-683c81f6c3d8.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.
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.
After creating the class add the urls.py file under your accounts django app.
/Discord/125/content_image/83895d32-334e-40a1-8a78-51f75dca061d.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')),
]/Discord/125/content_image/9cd74d4d-0a41-4f4b-a1d0-a2199a4b538d.png)
A sample urls.py(core folder) file can be found in the following git repository file.
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()),
]/Discord/125/content_image/c5ab52e1-8f28-44f9-a07f-d394d468e13e.png)
A sample urls.py(accounts) file can be found in the following git repository file.
Before run the django project . Run the following commands to migrate the models
python manage.py makemigrations
python manage.py migrateTo run the Django project . Run the following command
python manage.py runserverTest the API using postman or any other API Client tool.
/Discord/125/content_image/02e36312-efec-4436-898f-67fa2ba4dfeb.png)
Before we create Next App we need to install node.js.
If you already installed node.js please proceed to next step
After installing Node.js open the terminal or command prompt and create the next.js app with the following commands
npx create-next-app frontendAfter creating the Nextjs app then install next-auth using the following command.
npm i next-authIn 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.
/Discord/125/content_image/eb8f413a-766d-4c9c-bb7b-7726c5e50de6.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.
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.
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.
You can run your app via CLI with the following command and view it in your browser:
npm run devAfter 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.
/Discord/125/content_image/8b02292f-31b3-4226-a9f5-133b46c50c0a.png)
Click Sign in to Discord button , it will redirect to discord authorize page . click Authorize
/Discord/125/content_image/4d33f6d8-ada3-4a9e-9e0a-e975e6413922.png)
If authorization is success, then you will get the email and token .
/Discord/125/content_image/964f04f3-74ca-484f-bd7b-f296990408ae.png)
Comments