Email + Social Logins in Django - Step by Step Guide - GeeksforGeeks

25 Aug, 2021
This article revolves around a Django Project. It includes Email + Social Login integration in any Django project. We have used React as front end to demonstrate the working of the project. You can use your own front end to int
We need to  register a user we need to pass username, email , first_name , password (because of our model) .We need to  log in that user and authenticate user.User should be able to login with multiple Social Platforms.We need to make API for all the requirements.
We need to  register a user we need to pass username, email , first_name , password (because of our model) .
We need to  log in that user and authenticate user.
User should be able to login with multiple Social Platforms.
We need to make API for all the requirements.
Before starting, we need to know that we will send authenticated requests by sending a Bearer token in Authorization headers of the request .This token will tell the server which user sent the request. For that we will do these:
We will send the username  and password to a endpoint to get the token and this will serve as our loginIn return we will get access token and refresh token, we will set the authorization header of all requests to this access tokenHowever access token will expire after a short time, then we will send the refresh token to the endpoint to get a new access tokenThus we will repeat  step 2 and 3 internally without letting the user know when the access token expire
We will send the username  and password to a endpoint to get the token and this will serve as our login
In return we will get access token and refresh token, we will set the authorization header of all requests to this access token
However access token will expire after a short time, then we will send the refresh token to the endpoint to get a new access token
Thus we will repeat  step 2 and 3 internally without letting the user know when the access token expire
Basic knowledge of Django – Django Tutorial 
Create a  django project (inside a virtual environment preferably) – Create a Demo Project in Django 
Postman installed in your system (you can download it from here or use any alternative of postman)
After you have created  a demo project. Create a app Accounts.
python manage.py startapp accounts
Then you can see that a new folder is created with accounts name, now lets add it to the INSTALLED_APPS in settings.py. So it should look something like this:
INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
    # add this
   'accounts',
]
Inside the models.py lets create the model Account and its Manager & also import these:
from django.db import models
from django.contrib.auth.models import AbstractBaseUser,PermissionsMixin,BaseUserManager
from django.utils.translation import gettext_lazy as _

class Account(AbstractBaseUser,PermissionsMixin):
   email=models.EmailField(unique=True)
   username= models.CharField(_('User Name'),max_length=150)
   first_name = models.CharField(_('First Name'),max_length=150)
   last_name = models.CharField(_('last Name'),max_length=150)
   is_staff=models.BooleanField(default=False)
   is_active=models.BooleanField(default=True)

   objects=CustomAccountManager()

   USERNAME_FIELD='email'
   REQUIRED_FIELDS=['username','first_name']

   def __str__(self):
       return self.email
here we are using email as default USERNAME_FIELD and we want username and first_name as required fields (you can also have any other fields but username field should be there
is_active should be True by default and is_staff should be False by default
for creating objects(users) we need a Custom manager (which we are creating below)
write “username” instead of user_name or any other style because facebook or google login returns username
using gettextlazy is optional
class CustomAccountManager(BaseUserManager):
    def create_user(self,email,username,first_name,password,**other_fields):
        if not email:
            raise ValueError(_('Please provide an email address'))
        email=self.normalize_email(email)
        user=self.model(email=email,username=username,first_name=first_name,**other_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self,email,username,first_name,password,**other_fields):
        other_fields.setdefault('is_staff',True)
        other_fields.setdefault('is_superuser',True)
        other_fields.setdefault('is_active',True)
        if other_fields.get('is_staff') is not True:
                raise ValueError(_('Please assign is_staff=True for superuser'))
        if other_fields.get('is_superuser') is not True:
                raise ValueError(_('Please assign is_superuser=True for superuser'))
        return self.create_user(email,username,first_name,password,**other_fields)
for creating users we need to pass email, username, first_name and password as well as any other fields
if no email  then raise error otherwise normalize email (Read here)
then create a model Account object with email, username, and other fields
then set password, set_password actually sets the password as a hashed password in the model’s object so no one can see the actual password
then save the user object and return it
is_staff, is_superuser, is_active should be set to  True by default
if not set to True or is passed False  then raise errors else create_user with this values as well as other fields
Now add this in settings.py to use our custom user model :
AUTH_USER_MODEL='accounts.Account'
Then we need to migrate this model to database.
python manage.py makemigrations
python manage.py migrate
For that we need to install some libraries first which I will explain below why we need them:
pip install djangorestframework
pip install django-cors-headers
pip install drf_social_oauth2
djangorestframework is for REST API Endpoints
django-cors-headers is required  so that our React app can communicate with the django server
drf_social_oauth2 – this is the main library which enables us oauth2 token based authentication for email password as well as google and facebook
Now inside the settings.py we should add these to INSTALLED_APPS  for our app to work as expected:
INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
   # add these 
   'rest_framework',
   'oauth2_provider',
   'social_django',
   'drf_social_oauth2',
    'corsheaders',
   # LOCAL
   'accounts',
]
Let’s also add these configurations to settings.py which I’ll explain below:
AUTHENTICATION_BACKENDS = (  
  'drf_social_oauth2.backends.DjangoOAuth2',
  'django.contrib.auth.backends.ModelBackend',
)
REST_FRAMEWORK = {
   'DEFAULT_AUTHENTICATION_CLASSES': (
       'oauth2_provider.contrib.rest_framework.OAuth2Authentication',  
       'drf_social_oauth2.authentication.SocialAuthentication',
   )
}
CORS_ALLOWED_ORIGINS = [
   "http://localhost:3000",
   "http://127.0.0.1:3000"
]
AUTHENTICATION_BACKENDS – so that we can authenticate with either OAuth2(token based) or basic authentication(without token)
REST_FRAMEWORK endpoint requests can be authenticated using tokens only
CORS_ALLOWED_ORIGINS will be our frontend’s address (here it’s react  website’s address)
Alright now let’s add some configuration to the MIDDLEWARE and TEMPLATES :
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',
    # add these 
   'corsheaders.middleware.CorsMiddleware',
   'django.middleware.common.CommonMiddleware',
]

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',
                   # add these 
                'social_django.context_processors.backends',
               'social_django.context_processors.login_redirect',
           ],
       },
   },
]
Those are required by social logins and cors
Let’s add some URL to django project’s urls.py as those are required by both rest framework and oauth
from django.contrib import admin
from django.urls import path,include

urlpatterns = [
   path('admin/', admin.site.urls), 
    # add these 
   path('api-auth/', include('rest_framework.urls')),
   path('api-auth/', include('drf_social_oauth2.urls',namespace='drf')),
]
For Registering / Signing Up a user we need to write a serializer and view . Note that after signing up , that user needs to login by himself , that won’t happen automatically. However I have covered that as well! So a user can register and our backend will login that user!!
Let’s create the serializer, I have already created a serializers.py in accounts folder , so now write this code:
from rest_framework import serializers
from .models import Account

class RegistrationSerializer(serializers.ModelSerializer):
   class Meta:
       model=Account
       fields=('email','username','password','first_name')
       extra_kwargs={'password':{'write_only':True}}

   def create(self,validated_data):
       password=validated_data.pop('password',None)
       instance=self.Meta.model(**validated_data)
       if password is not None:
           instance.set_password(password)
       instance.save()
       return instance
Inside the RegistrationSerializer :
class Meta has what model , fields will be serialized  and password cannot be read it will only be write only (for forms )
create method will create new instance of Account and will fill that with validated data which we pass from our view
we need that password  and if that password is not None  we can set hashed password so we will pop it out of the validated data
then we will save the instance and return it
Now we will write the view for creating users:
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework import status,generics
from rest_framework.response import Response  
from .serializers import RegistrationSerializer
from rest_framework import permissions
from .models import Account

class CreateAccount(APIView):
   permission_classes=[permissions.AllowAny]

   def post(self,request):
       reg_serializer=RegistrationSerializer(data=request.data)
       if reg_serializer.is_valid():
           new_user=reg_serializer.save()
           if new_user:
              return Response(status=status.HTTP_201_CREATED)
       return Response(reg_serializer.errors,status=status.HTTP_400_BAD_REQUEST)
we import the necessary , allow anyone to access this view and only post requests to this view is allowed
when post request happens we will initialize RegistrationSerializer with request.data and if the data is valid then save it else return error
*Note : we will revisit this view to login the user after saving the serialized data later on
also create a urls.py in accounts directory and write this
from django.urls import path
from .views import CreateAccount

app_name = 'users'

urlpatterns = [
   path('create/', CreateAccount.as_view(), name="create_user"),]
finally add this url to project’s urls.py
from django.contrib import admin
from django.urls import path,include

urlpatterns = [
   path('admin/', admin.site.urls),  
   path('api-auth/', include('rest_framework.urls')),
   path('api-auth/', include('drf_social_oauth2.urls',namespace='drf')),
   #add this 
   path('api-auth/', include('accounts.urls'))
]
Now for login we don’t need to write any extra code as login is nothing but getting access token from server and all these is already taken care of by drf-social-oauth2. We don’t even need to write any endpoints for that !
We need to create a Application and get client_id and client_secret to get access tokens, So let’s visit admin running on http://127.0.0.1:8000/admin/ and you should see something like this:
Admin
Here you can see  Applications, press the add button of that and create a new application. Don’t touch the Client id and Client secret, select user as your admin superuser, and Client type will be confidential, Authorization grant type will be Resource owner password-based , then save it.
That’s it we are ready to check our application now so let’s create a superuser and pass the required values and then run server inside virtual environment
python manage.py runserver
Now open Postman and write this URL and in the body write this and send the request!
Postman post request to create user
You should get a blank response like this
Status 201 created
Thus the user is created so it is working fine!
For sending request for token we will pass client_id, client_secret,  email as username, password and grant_type=password to let server know this is for getting fresh new access_token and refresh token
Now we can send requests for token, so back in postman send this request to http://127.0.0.1:8000/api-auth/token :
Reqsuest returns Token
Note that we pass email to username field in request as passing username is mandatory
It should return access token and refresh token as well as status of 200 Ok
This access_token can be set on Authorization headers of request to send Authenticated requests (we will do that with react)
Remember I said we will revisit that CreateAccount view to login the user after saving the serialized data , Lets do that
import requests # add this

class CreateAccount(APIView):
   permission_classes=[permissions.AllowAny]

   def post(self,request):
       reg_serializer=RegistrationSerializer(data=request.data)
       if reg_serializer.is_valid():
           new_user=reg_serializer.save()
           if new_user:
              #add these
               r=requests.post('http://127.0.0.1:8000/api-auth/token', data = {
                   'username':new_user.email,
                   'password':request.data['password'],
                   'client_id':'Your Client ID',
                   'client_secret':'Your Client Secret',
                   'grant_type':'password'
               })
               return Response(r.json(),status=status.HTTP_201_CREATED)
       return Response(reg_serializer.errors,status=status.HTTP_400_BAD_REQUEST)
So after saving the new user we will send a post request to get the token and send it back as Response. This will automatically login the user after signup! 
Google and Facebook authentications need a frontend for complete demonstrations so I will show you that with react , however any frontend can do if you know what to do ! (I’ll do that after the necessary steps) 
We will create a different serializer which will return info about users and current user through two different views, one will be authenticated request and another will be a non-authenticated request.
so the serializer will look like this :
class UsersSerializer(serializers.ModelSerializer):
   class Meta:
       model=Account
       fields=('email','username','first_name')
We will write this two views :
AllUsers returns all users and any one can view data
CurrentUser which returns only the current user and only authenticated requests allowed
from rest_framework import status,generics

class AllUsers(generics.ListAPIView):
   permission_classes=[permissions.AllowAny]
   queryset=Account.objects.all()
   serializer_class=UsersSerializer

class CurrentUser(APIView):
   permission_classes = (permissions.IsAuthenticated,)
   def get(self, request):
       serializer = UsersSerializer(self.request.user)
       return Response(serializer.data)
urls.py will look like this :
from django.urls import path
from .views import CreateAccount,AllUsers,CurrentUser

app_name = 'users'

urlpatterns = [
   path('create/', CreateAccount.as_view(), name="create_user"),
   path('all/', AllUsers.as_view(), name="all"),
   path('currentUser/', CurrentUser.as_view(), name="current"),
   ]
So let’s send non-authenticated request first and it returns this response (Note that Authorization header has nothing in it)
Now let’s get the access token we previously received on step 3 
set the Authorization type to Bearer token from dropdown
send a authenticated request to http://127.0.0.1:8000/api-auth/currentUser/
It will return the current user now if you have by chance sent the request without Authorization header it would have returned like this
Now what if the access token expires what to do then ??
For that we need to send refresh token to the same endpoint to get new access and refresh token
Let me show you how ! So we send the request to /token with client_id, client_secret, grant_type = refresh_token (so that server understands request has a refresh token which then converts old access and refresh token to new access and refresh token ) and refresh_token will be the token we received in third step
It returns new tokens as a response you can use this new access token as Bearer token to Authenticate requests
Add these in settings.py and to get required keys for facebook and google you need to visit here for fb and here for google and perform the necessary steps there.
AUTHENTICATION_BACKENDS = (  
   'social_core.backends.google.GoogleOAuth2',
   'social_core.backends.facebook.FacebookAppOAuth2',
   'social_core.backends.facebook.FacebookOAuth2',
   
  'drf_social_oauth2.backends.DjangoOAuth2',
  'django.contrib.auth.backends.ModelBackend',
)
# Facebook configuration
SOCIAL_AUTH_FACEBOOK_KEY = 'your facebook key'
SOCIAL_AUTH_FACEBOOK_SECRET = 'your facebook secret'

# Define SOCIAL_AUTH_FACEBOOK_SCOPE to get extra permissions from Facebook.
# Email is not sent by default, to get it, you must request the email permission.
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {
   'fields': 'id, name, email'
}
SOCIAL_AUTH_USER_FIELDS=['email','first_name','username','password']

SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = "your google oauth2 key"
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = "your google oauth2 secret"

# Define SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE to get extra permissions from Google.
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [
   'https://www.googleapis.com/auth/userinfo.email',
   'https://www.googleapis.com/auth/userinfo.profile',
]
Before using this part of tutorial, checkout React JS Tutorial. After you have made a react App, perform the following steps – 
Its very easy to authenticate once you have the keys , now we need to install axios , react-facebook-login , react-google-login
Then create a login component where we render these fb and google login button which will take required keys and every user when tries to login with these buttons, will return an access token as well as info about user
import ReactFacebookLogin from "react-facebook-login";
import ReactGoogleLogin from "react-google-login";
import { facebookLogin, googleLogin } from "../axios";# I'll create this later

export default function LogIn() {
 
  function responseFb(response) {
    console.log(response);
    facebookLogin(response.accessToken);
  }
  function responseGoogle(response) {
    console.log(response);
    googleLogin(response.accessToken);
  }
  
  return (
    <>
    <ReactFacebookLogin
           appId="Your App Id"
           fields="name,email"
           callback={responseFb}
         />
         <ReactGoogleLogin
           clientId="your google client id"
           buttonText="Login"
           onSuccess={responseGoogle}
           onFailure={responseGoogle}
           cookiePolicy={"single_host_origin"}
         />
    </>
  );
}
However our server has no clue that we have logged in a user as this access token is returned from google , facebook and not from our server.
So how to let our server know ? Send this token to our  server or set this token as our Authorized Bearer token ? No in that case we cannot create a account object with our Accounts model or access any account object as current user thus our user will not be technically registered or connected.
So we need to convert this token to get access token, refresh token from our server, (registering our user) and for that I created a axios.js file and there I put this code:
export function facebookLogin(accessToken) {
 axios
   .post(`http://127.0.0.1:8000/api-auth/convert-token`, {
     token: accessToken,
     backend: "facebook",
     grant_type: "convert_token",
     client_id: "your client id",
     client_secret:"your client secret ",
   })
   .then((res) => {
    // Save somewhere these access and refresh tokens
     console.log(res.data);
   });
}
export function googleLogin(accessToken) {
 axios
   .post(`http://127.0.0.1:8000/api-auth/convert-token`, {
     token: accessToken,
     backend: "google-oauth2",
     grant_type: "convert_token",
     client_id: "your client id",
     client_secret: "your client secret",
   })
   .then((res) => {
    // Save somewhere these access and refresh tokens
     console.log(res.data);
   });
}
what we are doing here is:
make a post request to http://127.0.0.1:8000/api-auth/convert-token to convert the tokenbackend will be the backend from where you got the access token to convertgrant_type will be convert_token to let server know we want this token to be converted to our server’s access tokenclient_id and client_secret will be like previous steps
make a post request to http://127.0.0.1:8000/api-auth/convert-token to convert the token
backend will be the backend from where you got the access token to convert
grant_type will be convert_token to let server know we want this token to be converted to our server’s access token
client_id and client_secret will be like previous steps
So how to refresh a token if access tokens expire ?? We want that to happen automatically without letting the user bothered.
For that we will use axios interceptors which will return responses but if there is error it will handle those error
if error status is 401 (Unauthorized request) then if there is no refresh token in local storage then it will ask the user to login , and if refresh token exists then it will send that to http://127.0.0.1:8000/api-auth/token to get new access and refresh tokens
Please note I am using local storage to store access tokens as an example but you should a secure method probably Web Cookies (Secure, HttpOnly, Same Site)
window needs to be reloaded to let react fetch the website authenticates with latest access token
axiosInstance.interceptors.response.use(
 (response) => {
   return response;
 },
 async function (error) {
   const originalRequest = error;
   console.log(originalRequest);

   if (typeof error.response === "undefined") {
     alert("a server error happNeD, we will fix it shortly");
     return Promise.reject(error);
   }

   if (
     error.response.status === 401 &&
     !localStorage.getItem("refresh_token")
   ) {
     window.location.href = "/login/";
     return Promise.reject(error);
   }

   if (
     error.response.status === 401 &&
     error.response.statusText === "Unauthorized" &&
     localStorage.getItem("refresh_token") !== undefined
   ) {
     const refreshToken = localStorage.getItem("refresh_token");

     return axios
       .post("http://127.0.0.1:8000/api-auth/token", {
         client_id: "Your client id ",
         client_secret:
           "Your client secret",
         grant_type: "refresh_token",
         refresh_token: refreshToken,
       })
       .then((response) => {
         localStorage.setItem("access_token", response.data.access_token);
         localStorage.setItem("refresh_token", response.data.refresh_token);
         window.location.reload();
         axiosInstance.defaults.headers["Authorization"] =
           "Bearer " + response.data.access_token;
       })
       .catch((err) => console.log(err));
   }
 }
);
This way we can authenticate a user from Django and React using social logins.
kalrap615
sweetyty
Python Django
Technical Scripter 2020
Python
Technical Scripter
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Comments
Old Comments
How to Install PIP on Windows ?
How to drop one or multiple columns in Pandas Dataframe
How To Convert Python Dictionary To JSON?
Check if element exists in list in Python
Python | Pandas dataframe.groupby()
Python | Get unique values from a list
Defaultdict in Python
Python | os.path.join() method
Python Classes and Objects
Create a directory in Python