Django Authentication With Facebook, Instagram and LinkedIn

<strong>In this tutorial we will implement Facebook, Instagram and LinkedIn authentication using the social-auth-app-django library.</strong>

In this tutorial we will implement Facebook, Instagram and LinkedIn authentication using the social-auth-app-django library.


Introduction

Table of Contents

For users of internet services, there are only a few things worse than having to manually sign in (and sign-up) with different websites. The ordeal of the manual sign-in process can be associated with the difficulty that the user may experience in having to remember multiple strong passwords.

The inability to remember a password could lead to the creation of multiple accounts on the same platform or the outright switch to browsing as an unauthenticated user (where it doesn’t prevent access to the sought-after information or service).

Modern web applications solve this problem using social authentication, which is primarily a way to allow users to sign in (and sign-up) with the application using login information from a social network provider that they already have an account with.

In this tutorial, we will build a simple Django application that allows users to sign in via their Facebook, Instagram and LinkedIn accounts. To achieve this, we will use the social-auth-app-django library. We will also learn how to extract additional information such as profile picture and username from the respective social accounts.

At the end of this tutorial, we will have the final application that works like this:

The source code for this project is available here on GitHub.


Prerequisites

You need the following items installed on your machine to follow along with this tutorial:

  1. Python3
  2. Pipenv
Pipenv is a production-ready tool that aims to bring the best of all packaging worlds to the Python world. It harnesses Pipfile, pip, and virtualenv into one single command.

This tutorial assumes that the reader has basic working knowledge with Django. You also need to have an account with Facebook, Instagram and LinkedIn.

Let’s dive right in!


Setting up the Django app

In this section, we will up a new Django project and install dependencies. Let’s start by creating a new folder and making it the present working directory:

    $ mkdir django_social_app
    $ cd django_social_app

We will create and activate a new virtual environment using Pipenv; this has the effect of creating an isolated Python environment and preventing us from polluting the global package directory when we install Django dependencies. We will also install django and social-auth-app-django:

    $ pipenv shell
    $ pipenv install django social-auth-app-django
social-auth-app-django simplifies the implementation of social authentication with Django.

Let’s create (and navigate into) a new Django project, we will call it social_app:

    $ (django_social_app) $ django-admin startproject social_app
    $ (django_social_app) $ cd social_app
Note: It is important that we run the commands from the terminal that is sourced into the virtual environment i.e displays (django_social_app) at the beginning of each command line.

Next, we will create a Django application called core, this application will contain all our views and templates:

    (django_social_app) $ python manage.py startapp core
Note: You need to be in the parent social_app directory to run ‘python manage.py *’ commands.

Let’s find the settings.py file in the social_app project and add both core and social-auth-app-django as INSTALLED_APPS:

    #social_app/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'social_django', # add this 
    'core' # add this
  ]

Finally, let’s migrate the database:

    (django_social_app) $ python manage.py migrate

Configuring the authentication classes

Behind the scenes, Django maintains a list of “authentication backends” that it checks during user authentication. If the first authentication method fails, Django tries the second one, and so on, until all backends have been attempted.

The AUTHENTICATION_BACKENDS array contains a list of authentication backend classes (as strings) and is by default set to:

    ['django.contrib.auth.backends.ModelBackend']

We can update it and add new authentication classes in order to allow authentication with the social platforms we are considering in this tutorial.

To update it this, simply add the following code in the settings.py file:

    #social_app/settings.py

#add this
AUTHENTICATION_BACKENDS = [
    'social_core.backends.linkedin.LinkedinOAuth2',
    'social_core.backends.instagram.InstagramOAuth2',
    'social_core.backends.facebook.FacebookOAuth2',
    'django.contrib.auth.backends.ModelBackend',
]

We just added the authentication backend classes for Linkedin, Instagram and Facebook.

You can find a list of the authentication backends classes supported by social-auth-app-django here.

Adding templates and static files

We’ve only worked on setting up and configuring the application, let’s move on to something visual now. In this section, we will build the foundation of the templates that will display the application.

Let’s create a new folder in the coredirectory, we will call this folder templates:

    (django_social_app) $ cd core/
(django_social_app) $ mkdir templates/

Create three files within the templates directory and call them:

  1. base.html
  2. login.html
  3. home.html

Now, open the base.html file and paste in the following snippet:

    <!-- templates/base.html -->

{% load static %}
&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
    &lt;meta http-equiv="X-UA-Compatible" content="ie=edge" /&gt;
    &lt;link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
        crossorigin="anonymous" /&gt;
    &lt;link rel="stylesheet" href="{% static 'css/index.css' %}" /&gt;
    &lt;title&gt;Social Auth with Django&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div class="container-fluid"&gt;
        &lt;div&gt;
            &lt;h1 class="text-white text-center"&gt;{% block title %}{% endblock %}&lt;/h1&gt;
            &lt;div class="card p-5"&gt;
                {% block content %}
                {% endblock %}
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

Paste in the following snippet in the login.html file:

   <!-- templates/login.html -->

{% extends 'base.html' %}
{% block title %} Sign in {% endblock %}
{% block content %}
&lt;div class="row"&gt;
    &lt;div class="col-md-8 mx-auto social-container my-2 order-md-1"&gt;
        &lt;button class="btn btn-danger  mb-2"&gt;
            &lt;a href="#"&gt;Login with Instagram&lt;/a&gt;
        &lt;/button&gt;
        &lt;button class="btn btn-primary mb-2"&gt;
            &lt;a href="#"&gt;Login with Facebook
            &lt;/a&gt;
        &lt;/button&gt;
        &lt;button class="btn btn-info mb-2"&gt;
            &lt;a href="#"&gt;Login with LinkedIn&lt;/a&gt;
        &lt;/button&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
{% endblock %}

Lastly, update the home.html file with the code below:

    <!-- templates/home.html -->

{% extends 'base.html' %}
{% block title %} Home {% endblock %}
{% block content %}
&lt;div class="row"&gt;
    &lt;div class="col-sm-12 mb-3"&gt;
        &lt;h4 class="text-center"&gt; Welcome {{ user.username }} &lt;/h4&gt;
    &lt;/div&gt;
&lt;/div&gt;
{% endblock %}

We need some styles to help our code look nice when rendered, so let’s create a folder called static in the root of the core folder and we will store our styles there.

Create a folder called css folder within the static directory and finally, create an index.css file within the css folder.

Now open the index.css file and update it with the following code:

    /_ index.css _/

img {
  border: 3px solid #282c34;
}
.container-fluid {
  height: 100vh;
  background-color: #282c34;
  display: flex;
  justify-content: center;
  align-items: center;
}
.container-fluid &gt; div {
  width: 85%;
  min-width: 300px;
  max-width: 500px;
}
.card {
  width: 100%;
}
.social-container {
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.btn a, .btn a:hover {
  color: white;
  text-decoration: none ;
}

Setting up the Views and URLs

In this section, we will define the Views and register the URLs that the application needs to work, so open the core/views.py file and replace its content with the snippet below:

    # core/views.py

from django.shortcuts import render
from django.contrib.auth.decorators import login_required

# Create your views here.
def login(request):
  return render(request, 'login.html')

@login_required
def home(request):
  return render(request, 'home.html')

Next, we will register the routes for the application and attach their matching view functions. Replace the content of the social_app/urls.py file with the code below:

    # social_app/urls.py

from django.contrib import admin
from django.urls import path, include
from django.contrib.auth import views as auth_views
from core import views

urlpatterns = [
    path('admin/', admin.site.urls),         path("login/", views.login, name="login"),
    path("logout/", auth_views.LogoutView.as_view(), name="logout"),
    path('social-auth/', include('social_django.urls', namespace="social")),
    path("", views.home, name="home"),
]

In the settings.py file, we need to set four new values — LOGIN_URL, LOGOUT_URL, LOGIN_REDIRECT_URL and LOGOUT_REDIRECT_URL — because they will be used in redirecting the user when authentication is complete:

    # social_app/settings.py

# [...]

LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'home'
LOGOUT_URL = 'logout'
LOGOUT_REDIRECT_URL = 'login'

# [...]

Fantastic! We can now run the application to see what we’ve built so far. Let’s start the server with this command:

    (django_social_app) $ python manage.py runserver
Note: You need to be in the parent social_app directory to run ‘python manage.py ’ commands.

We can view the application on http://localhost:8000, though we will be redirected to /login since we aren’t authenticated:

Looking good! In the next sections, we will register our application with the social network providers so that users can be authenticated via social platforms.


Facebook Authentication

In this section, we will do the heavy lifting and set up authentication via Facebook.


Get Facebook Credentials

Head over to the Facebook Developers’ page, after signing in, click on Add a New App and enter the details for the app on the modal window that appears:

Once the app has been created, you will be redirected to the application’s dashboard. On the left side of the screen, click on* Settings**, then click on the Basic option that appears directly underneath it.

When the new screen loads, under the App Domains section, add localhost like this:

Now scroll down until you see an option that says Add Platform, Click on it and select the Website option. This will create a website section where you will see Site URL, add http://localhost:8000/ in the input and click on the Save Changes button:

Now, copy the App ID and App secret from the applications dashboard and add them to the settings.py file:

    # social_app/settings.py

#[...]

SOCIAL_AUTH_FACEBOOK_KEY = YOUR_APP_KRY        # App ID
SOCIAL_AUTH_FACEBOOK_SECRET = YOUR_APP_SECRET  # App Secret

#[...]

Replace the YOUR_APP_* keys with the values from your Facebook application dashboard.

Let’s update the URL of the Login with Facebook button in login.html file with this one:

    <!-- templates/login.html -->

        &lt;button class="btn btn-primary mb-2"&gt;
            &lt;a href="{% url 'social:begin' 'facebook' %}"&gt;Login with Facebook&lt;/a&gt;
        &lt;/button&gt;

Start up the web server and visit localhost:8000/login to test that we can now log into the application via Facebook:

    (django_social_app) $ python manage.py runserver
Note: You need to be in the parent social_app directory to run ‘python manage.py *’ commands.

When we click on the Login with Facebook button, we should be redirected to a page similar to the one below:

Clicking on Continue as USERNAME will redirect to the home page and your username will be displayed. We have successfully been authenticated via Facebook:

Want to know what just happened behind the scenes? Let’s find out; we will create a superuser account to access the admin panel:

    (django_social_app) $ python manage.py createsuperuser
You will get a prompt to enter a username, email and password for the superuser. Be sure to enter details that you can remember because you will need them to log in to the admin dashboard shortly.

After creating the superuser account, we will run the server and visit the admin panel on this address — http://localhost:8000/admin/:

We can see that a new User (besides the superuser) has been created in the Users category:

We will also see that there is a new account under the User social auths category:

The explanation for the existence of these new accounts is: When a user logs into the application using Facebook (or any social network provider), a new User and User Social Auth instance are created. The User Social Auth is linked to the User account the first time, then subsequently, the User instance is simply queried from the database.

The User instance is created using the details received from the social network provider. In this case, Facebook sent back the first_name and last_name fields of the created User Instance, and the username is a concatenation of the first_name and last_name.

Some social providers (like Instagram) will return the user’s username on their platform and this is used (instead of the concatenation) for the created User instance on the application.

We don’t want our application to just display a username, we want to get additional *_User *_data such as profile picture, so let’s request for extra data from Facebook.

Note: You will need to log out from the admin panel to continue.

Open the settings.py file and update it accordingly:

    # settings.py

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', # add this
                'social_django.context_processors.login_redirect', # add this
            ],
        },
    },
]

SOCIAL_AUTH_FACEBOOK_KEY = YOUR_APP_SECRET          # App ID
SOCIAL_AUTH_FACEBOOK_SECRET = YOUR_APP_SECRET       # App Secret
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email', 'user_link'] # add this
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {       # add this
  'fields': 'id, name, email, picture.type(large), link'
}
SOCIAL_AUTH_FACEBOOK_EXTRA_DATA = [                 # add this
    ('name', 'name'),
    ('email', 'email'),
    ('picture', 'picture'),
    ('link', 'profile_url'),
]

Replace the YOUR_APP_* keys with the values from your Facebook application dashboard.

The social_django context processors help in adding backend and associations data to template context. This makes it easy for us to access data about the authenticated user using template tags. You can read more about it here.

When a user logs into the application via Facebook, we can access a subset of the user’s data using permissions; permissions are how we ask if we can access the data on Facebook. In the code above, SOCIAL_AUTH_FACEBOOK_SCOPE contains a list of permissions to access the data properties our application requires. The email and user_link permissions request access to the user’s Facebook email and profile link respectively.

Let’s start the server now, visit http://localhost:8000/login/, and attempt logging in via Facebook:

Clicking on Continue as USERNAME will grant the application access to user’s private data on Facebook and for this reason, to request for some permissions, you will need to submit your application to be reviewed by the Facebook team to ensure that the returned data isn’t misused. You can find the list of Facebook permissions here.

SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS has a key — fields — where the value is a list of attributes that should be returned by Facebook when the user has successfully logged in. The values are dependent on SOCIAL_AUTH_FACEBOOK_SCOP .

When a user logs in using Facebook, the Facebook API returns the data requested in SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS. To store the data in the database, we need to specify them in SOCIAL_AUTH_FACEBOOK_EXTRA_DATA.

The extra data will be stored in the extra_datafield of the User Social Auth instance:

Now, let’s update the frontend to display the extra data we received from Facebook, we will open the home.html file and replace its content with this one:

<!-- home.html -->

{% extends 'base.html' %}
{% block title %} Home {% endblock %}
{% block content %}
  &lt;div class="row"&gt;
    &lt;div class="col-sm-12 mb-3"&gt;
      &lt;h4 class="text-center"&gt;Welcome {{ user.username }}&lt;/h4&gt;
    &lt;/div&gt;

        &lt;!-- Add from here --&gt;
    {% for ass in backends.associated %}
      {% if ass.provider == 'facebook' %}
        &lt;div class="col-md-6 text-center"&gt;
            &lt;img src="{{ass.extra_data.picture.data.url}}" alt="" width="150" height="150" style="border-radius: 50%"&gt;
        &lt;/div&gt;
        &lt;div class="col-md-6 social-container my-2"&gt;                 &lt;p&gt; Signed in via:  {{ ass.provider }} &lt;/p&gt;
            &lt;p&gt; Name:  {{ ass.extra_data.name }} &lt;/p&gt;
            &lt;p&gt; Provider url: &lt;a href="{{ass.extra_data.profile_url}}"&gt;link&lt;/a&gt;&lt;/p&gt;
        &lt;/div&gt;
      {% endif %}
    {% endfor %}
    &lt;div class="col-sm-12 mt-2 text-center"&gt;
      &lt;button class="btn btn-danger"&gt;
        &lt;a href="{% url 'logout' %}"&gt;Logout&lt;/a&gt;
      &lt;/button&gt;
    &lt;/div&gt;
        &lt;!-- End here --&gt;
  &lt;/div&gt;
{% endblock %}

Now we can visit the application’s home address — http://localhost:8000/ — on a web browser and see the extra data:

Note: We now have access to the data that gets stored in the extra_data field because we added the context processors to TEMPLATES; this enables us to access backends data in the frontend.

Congratulations! we have successfully authenticated via Facebook and can now move on to setting up the next social network provider.


Instagram Authentication

Seeing that we’ve gone into details on how things work behind the scene, we can set up the Instagram authentication in just three steps:

  • Get API credentials
  • Setup the backend
  • Setup the frontend

Let’s go!


Get API credentials

Let’s visit the Instagram developers page and click on Register Your Application:

On the screen that comes up next, click on Register a New Client and fill out the details for the application:

Note: Instagram (and most social network providers) require a redirect URL which will be the address the user’s browser will be directed to after the authentication. For this tutorial, we will use this address http://localhost:8000/social-auth/complete/instagram/. You can learn more about this here.

Successful registration of the application will return a screen like this:

Now, we will click on the Manage option to get the application’s Client ID and Client Secret:

Note: We need the ID and the Secret to configure the backend.

Setup the Backend

Let’s add this code to the settings.py file:

    # settings.py

#[...]

# add this code
SOCIAL_AUTH_INSTAGRAM_KEY = YOUR_CLIENT_ID         #Client ID
SOCIAL_AUTH_INSTAGRAM_SECRET = YOUR_CLIENT_SECRET  #Client SECRET
SOCIAL_AUTH_INSTAGRAM_EXTRA_DATA = [         ('user', 'user'),
]

#[...]

Replace the YOUR_CLIENT_* keys with the values from your Instagram application.

With the code above, once a user is authenticated via Instagram, a user object will be sent back to the application. We add this object to SOCIAL_AUTH_INSTAGRAM_EXTRA_DATA because we want it to be stored in the database for easy reference.

The user object is returned in this format:

    {
"id": ...,
"username": ...,
"profile_picture": ...,
"full_name": ...,
"bio": ...,
"website": ...,
"is_business": ...,
}

Setup the Frontend

We want to display a nice UI for when a user is authenticated via Instagram so let’s replace the content of the home.html file with the code below:

   <!-- home.html -->

{% extends 'base.html' %}
{% block title %} Home {% endblock %}
{% block content %}
&lt;div class="row"&gt;
    &lt;div class="col-sm-12 mb-3"&gt;
        &lt;h4 class="text-center"&gt;Welcome {{ user.username }}&lt;/h4&gt;
    &lt;/div&gt;
    &lt;!-- Add from here --&gt;
    {% for ass in backends.associated %}
      {% if ass.provider == 'facebook' %}
        &lt;div class="col-md-6 text-center"&gt;
            &lt;img src="{{ass.extra_data.picture.data.url}}" alt="" width="150" height="150" style="border-radius: 50%"&gt;
        &lt;/div&gt;
        &lt;div class="col-md-6 social-container my-2"&gt;                 &lt;p&gt; Signed in via:  {{ ass.provider }} &lt;/p&gt;
            &lt;p&gt; Name:  {{ ass.extra_data.name }} &lt;/p&gt;
            &lt;p&gt; Provider url: &lt;a href="{{ass.extra_data.profile_url}}"&gt;link&lt;/a&gt;&lt;/p&gt;
        &lt;/div&gt;
      {% endif %}
      &lt;!-- Add from here --&gt;
      {% if ass.provider == 'instagram' %}
        &lt;div class="col-md-6 text-center"&gt;               &lt;img src="{{ ass.extra_data.user.profile_picture }}" alt="" width="150" height="150" style="border-radius: 50%"&gt;             &lt;/div&gt;
        &lt;div class="col-md-6 social-container my-2"&gt;
          &lt;p&gt;Signed in via: {{ ass.provider }} &lt;/p&gt;
          &lt;p&gt; Name:  {{ ass.extra_data.user.full_name }} &lt;/p&gt;
          &lt;p&gt;Provider url: &lt;a href="https://instagram.com/{{ ass.username }}"&gt;link&lt;/a&gt;&lt;/p&gt;
      {% endif %}
      &lt;!-- End here --&gt;
    {% endfor %}
    &lt;div class="col-sm-12 mt-2 text-center"&gt;
        &lt;button class="btn btn-danger"&gt;
            &lt;a href="{% url 'logout' %}"&gt;Logout&lt;/a&gt;
        &lt;/button&gt;
    &lt;/div&gt;
    &lt;!-- End here --&gt;
&lt;/div&gt;
{% endblock %}

Let’s update the URL of the *_Login with Instagram *_button in login.html file:

    <!-- templates/login.html -->

    &lt;button class="btn btn-danger  mb-2"&gt;
        &lt;a href="{% url 'social:begin' 'instagram' %}"&gt;Login with Instagram&lt;/a&gt;
    &lt;/button&gt;

We can now start the server, visit http://localhost:8000/login, and try to login with Instagram:

Note: The Instagram application is in sandbox (test) mode. To make it live, you will have to submit it for a review. You can learn more about this here.

Once the application is authorized, the user will be logged in and redirected to the home page:


Linkedin Authentication

We will set up LinkedIn authentication in three steps:

  • Get API credentials
  • Setup the Backend
  • Setup the Frontend

Get API credentials

Let’s visit the Linkedin developers page and click on Create app:

Fill out the application details:

Note: Most of these fields are required and you will have to fill them out with valid structured data.

Once the app has successfully been created, you will be redirected to the application’s dashboard. Here, add http://localhost:8000/social-auth/complete/linkedin-oauth2/ as the redirect URL and update the application:

Take note of the Client ID and Secret, we will need it in setting up the backend.


Setup the Backend

Let’s add this code to the settings.pyfile:

    # settings.py

#[...]

# add this code
SOCIAL_AUTH_LINKEDIN_OAUTH2_KEY = YOUR_CLIENT_ID         #Client ID
SOCIAL_AUTH_LINKEDIN_OAUTH2_SECRET = YOUR_CLIENT_SECRET  #Client Secret
SOCIAL_AUTH_LINKEDIN_OAUTH2_SCOPE = ['r_basicprofile', 'r_emailaddress']
SOCIAL_AUTH_LINKEDIN_OAUTH2_FIELD_SELECTORS = ['email-address', 'formatted-name', 'public-profile-url', 'picture-url']
SOCIAL_AUTH_LINKEDIN_OAUTH2_EXTRA_DATA = [
    ('id', 'id'),
    ('formattedName', 'name'),
    ('emailAddress', 'email_address'),
    ('pictureUrl', 'picture_url'),
    ('publicProfileUrl', 'profile_url'),
]

#[...]

Replace the YOUR_CLIENT_* keys with the values from your LinkedIn application.

The SOCIAL_AUTH_LINKEDIN_OAUTH2_SCOPE array contains the permissions needed to access the user’s data, similar to what we saw when we set up authentication via Facebook.

The SOCIAL_AUTH_LINKEDIN_OAUTH_FIELD_SELECTORS array contains the list of data that should be returned when the user is successfully authenticated via Linkedin. It is similar to the SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS array for Facebook. You can find the full list of data items that can be requested for here.

The SOCIAL_AUTH_LINKEDIN_OAUTH2_EXTRA_DATA array contains the data that we want to store in the database for later reference.


Setting up Frontend

Let’s replace the content of the home.html file with the code below:

    <!-- home.html -->

{% extends 'base.html' %}
{% block title %} Home {% endblock %}
{% block content %}
&lt;div class="row"&gt;
    &lt;div class="col-sm-12 mb-3"&gt;
        &lt;h4 class="text-center"&gt;Welcome {{ user.username }}&lt;/h4&gt;
    &lt;/div&gt;
    &lt;!-- Add from here --&gt;
    {% for ass in backends.associated %}
      {% if ass.provider == 'facebook' %}
        &lt;div class="col-md-6 text-center"&gt;
            &lt;img src="{{ass.extra_data.picture.data.url}}" alt="" width="150" height="150" style="border-radius: 50%"&gt;
        &lt;/div&gt;
        &lt;div class="col-md-6 social-container my-2"&gt;                 &lt;p&gt; Signed in via:  {{ ass.provider }} &lt;/p&gt;
            &lt;p&gt; Name:  {{ ass.extra_data.name }} &lt;/p&gt;
            &lt;p&gt; Provider url: &lt;a href="{{ass.extra_data.profile_url}}"&gt;link&lt;/a&gt;&lt;/p&gt;
        &lt;/div&gt;
      {% endif %}
      {% if ass.provider == 'instagram' %}
        &lt;div class="col-md-6 text-center"&gt;               &lt;img src="{{ ass.extra_data.user.profile_picture }}" alt="" width="150" height="150" style="border-radius: 50%"&gt;             &lt;/div&gt;
        &lt;div class="col-md-6 social-container my-2"&gt;
          &lt;p&gt;Signed in via: {{ ass.provider }} &lt;/p&gt;
          &lt;p&gt; Name:  {{ ass.extra_data.user.full_name }} &lt;/p&gt;
          &lt;p&gt;Provider url: &lt;a href="https://instagram.com/{{ ass.username }}"&gt;link&lt;/a&gt;&lt;/p&gt;
      {% endif %}
      &lt;!-- Add from here --&gt;
      {% if ass.provider == 'linkedin-oauth2' %}
        &lt;div class="col-md-6 text-center"&gt;
            &lt;img src="{{ass.extra_data.picture_url}}" alt="" width="150" height="150" style="border-radius: 50%"&gt;
        &lt;/div&gt;
        &lt;div class="col-md-6 social-container my-2"&gt;                 &lt;p&gt; Signed in via:  Linkedin &lt;/p&gt;
            &lt;p&gt; Name:  {{ ass.extra_data.name }} &lt;/p&gt;
            &lt;p&gt; Provider url: &lt;a href="{{ass.extra_data.profile_url}}"&gt;link&lt;/a&gt;&lt;/p&gt;
        &lt;/div&gt;
      {% endif %}
      &lt;!-- End here --&gt;
    {% endfor %}
    &lt;div class="col-sm-12 mt-2 text-center"&gt;
        &lt;button class="btn btn-danger"&gt;
            &lt;a href="{% url 'logout' %}"&gt;Logout&lt;/a&gt;
        &lt;/button&gt;
    &lt;/div&gt;
    &lt;!-- End here --&gt;
&lt;/div&gt;
{% endblock %}

We will also update the URL of the Login with LinkedIn button in the login.html file:

    <!-- templates/login.html -->

    &lt;button class="btn btn-info mb-2"&gt;
       &lt;a href="{% url 'social:begin' 'linkedin-oauth2' %}"&gt;Login with LinkedIn&lt;/a&gt;
    &lt;/button&gt;

We can now start the server, visit http://localhost:8000/login, and try to login with LinkedIn:

Once we authorize the application by clicking on Allow, we will be directed to the homepage:


Conclusion

We have come to the end of this tutorial and have learnt how to set up social authentication in Django using the social-auth-app-django library with minimal configuration. We have also learnt how to request extra user data once the user has been authenticated via a social network provider.

As we already discussed at the beginning of this article, the importance of social authentication in modern web applications cannot be overemphasized.

The code for this article is available here on GitHub.


Learn More

Python and Django Full Stack Web Developer Bootcamp

Django 2 & Python | The Ultimate Web Development Bootcamp

The Ultimate Beginner’s Guide to Django 1.11

Django Core | A Reference Guide to Core Django Concepts

Python Django Tutorial | Django Course

Python Django Tutorial | Django Course

🔥Intellipaat Django course: https://intellipaat.com/python-django-training/ 👉This Python Django tutorial will help you learn what is django web development &...

This Python Django tutorial will help you learn what is django web development & application, what is django and introduction to django framework, how to install django and start programming, how to create a django project and how to build django app. There is a short django project as well to master this python django framework.

Why should you watch this Django tutorial?

You can learn Django much faster than any other programming language and this Django tutorial helps you do just that. Our Django tutorial has been created with extensive inputs from the industry so that you can learn Django and apply it for real world scenarios.

Developing Restful APIs with Python, Django and Django Rest Framework

Developing Restful APIs with Python, Django and Django Rest Framework

This article is a definitive guide for starters who want to develop projects with RESTful APIs using Python, Django and Django Rest Framework.

This article is a definitive guide for starters who want to develop projects with RESTful APIs using Python, Django and Django Rest Framework.

Introduction
  • Django is a web framework written in Python
  • Python is an interpreted high-level programming language for general-purpose programming
  • API or Application Programming Interface is a set of rules and mechanisms by which one application or component interacts with the others
  • REST or Representational State Transfer is a software architecture

REST APIs

As described in a dissertion by Roy Fielding,

REST is an "architectural style' that basically exploits the existing technology and protocols of the web.
In simple definition, it is the data representation for a client in the format that is suitable for it.

Hence, RESTful + API is a commonly used terminology for the implementation of such architecture and constraints (eg. in web services).

Here is an example GET request from GitHub's API

$ curl https://api.github.com/users/joshuadeguzman

You will see an output similar to this

{
  "login": "joshuadeguzman",
  "id": 20706361,
  "node_id": "MDQ6VXNlcjIwNzA2MzYx",
  "avatar_url": "https://avatars1.githubusercontent.com/u/20706361?v=4",
  "gravatar_id": "",
  "url": "https://api.github.com/users/joshuadeguzman",
  "html_url": "https://github.com/joshuadeguzman",
  "followers_url": "https://api.github.com/users/joshuadeguzman/followers",
  "following_url": "https://api.github.com/users/joshuadeguzman/following{/other_user}",
  "gists_url": "https://api.github.com/users/joshuadeguzman/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/joshuadeguzman/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/joshuadeguzman/subscriptions",
  "organizations_url": "https://api.github.com/users/joshuadeguzman/orgs",
  "repos_url": "https://api.github.com/users/joshuadeguzman/repos",
  "events_url": "https://api.github.com/users/joshuadeguzman/events{/privacy}",
  "received_events_url": "https://api.github.com/users/joshuadeguzman/received_events",
  "type": "User",
  "site_admin": false,
  "name": "Joshua de Guzman",
  "company": "@freelancer",
  "blog": "https://joshuadeguzman.me",
  "location": "Manila, PH",
  "email": null,
  "hireable": true,
  "bio": "Android Engineer at @freelancer. Building tools for humans.",
  "public_repos": 75,
  "public_gists": 2,
  "followers": 38,
  "following": 10,
  "created_at": "2016-07-28T15:19:54Z",
  "updated_at": "2019-06-16T10:26:39Z"
}

Shown above is a data set in JSON format.

JSON or JavaScript Object Notation is an open-standard file format that uses human-readable text to transmit data objects consisting of attribute–value pairs and array data types.
Other formats include XML, INI, CSV, etc. But today, JSON is widely use for its structure is intuitive, making it comfortable to read and map domain objects no matter what programming language is being used.

Python and Django

Python, according to its creator, Guido van Rossum, is a

high-level programming language, and its core design philosophy is all about code readability and a syntax which allows programmers to express concepts in a few lines of code.
Python uses english like words representation (eg. for methods, reserve keywords and control flow) that makes it easier for any beginner to jump right into it. It also features dynamic type system meaning it verifies the type safety of program at runtime. It also does automatic memory management.

print(5 + 5) # This will result to 10

Django is a high-level Python Web Framework that enables developers to deliver projects on time with clean and pragmatic design.

Its flagship features include a design for fast development, a secure and scalable product.

Quick Django Overview

Django's way of propagating changes to your database schema is by means of its migration modules.

Sample User model

from django.db import models

class User(models.Model):
    first_name = models.CharField(max_length=50)
    middle_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

If any changes are made on your models, run makemigrations

$ python manage.py makemigrations

Finally, you can synchronize the database with the set of models and migrations

$ python manage.py migrate

REST APIs with Django Rest Framework

DRF or Django REST Framework is a powerful and flexible toolkit for building Web APIs. It helps the developers to not reinvent the wheel by rolling out complex and solid REST API from scratch by themselves. Because when your projects become more and more complex, you will soon realise the need of using DRF or other helpful rest framework.

1. Installation & Project Setup

Create project directory

$ mkdir djangoapi

Install virtualenv via pip

A virtual environment enables a project to have additional libraries or changes in packages within its environment without disturbing global or libraries of other environments.

pip is a package management system used to install and manage software packages written in Python.

$ pip install virtualenv

To create an environment folder in your project's directory

$ cd djangoapi
$ virtualenv venv

To activate the environment

$ source venv/bin/activate

To undo these changes to your path, simply run deactivate. More on virtualenv.

Install django, djangorestframework

$ pip install django
$ pip install djangorestframework

Creating a django project

$ django-admin startproject blog

Running your project

$ python manage.py runserver

System check identified no issues (0 silenced).

You have 15 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

August 16, 2018 - 09:58:36
Django version 2.1, using settings 'blog.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

The unapplied migrations refer to the default migration files included when you start a django project.

To synchronize these migration files, simply run migrate

$ python manage.py migrate

Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying sessions.0001_initial... OK

The default database in our project is currently set to SQLite named db.sqlite3.

Creating a django project's app

$ cd blog
$ python manage.py startapp posts

The project structure should look like

$ find .
./posts
./posts/migrations
./posts/migrations/__init__.py
./posts/models.py
./posts/__init__.py
./posts/apps.py
./posts/admin.py
./posts/tests.py
./posts/views.py
./db.sqlite3
./blog
./blog/__init__.py
./blog/__pycache__
./blog/__pycache__/settings.cpython-36.pyc
./blog/__pycache__/wsgi.cpython-36.pyc
./blog/__pycache__/__init__.cpython-36.pyc
./blog/__pycache__/urls.cpython-36.pyc
./blog/settings.py
./blog/urls.py
./blog/wsgi.py
./manage.py

2. Model

Each model instance is a definitive source of the information about your data. In general, each model pertains to a single table in your database.

# djangoapi/blog/posts/models.py
from django.db import models

# Create your models here.

class Post(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    is_featured = models.BooleanField(default=False)

    def __str__(self):
        return self.name

__str__ is called by the str() built-in function and by the print statement to compute the "informal" string representation of an object.
If you try running makemigrations, django won't see those changes yet.

$ No changes detected

To solve this, add your posts app to your project's installed apps.

# djangoapi/blog/blog/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'posts' # Add it here
]

To continue with the migration of models

$ python manage.py makemigrations

Migrations for 'posts':
  posts/migrations/0001_initial.py
    - Create model Post

$ python manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, posts, sessions
Running migrations:
  Applying posts.0001_initial... OK


3. Serialization

Serializers allow data structure or object state to be translated into a format that can be stored or transmitted and be reconstructed later on.

Create API's serializers.py and views.py files and isolate them like this

# posts/api
posts/api/serializers.py
posts/api/views.py

# posts/migrations
posts/migrations/

# posts
posts/admin.py
posts/apps.py
posts/models.py
posts/tests.py
posts/views.py
# posts/api/serializers.py

from ..models import Post
from rest_framework import serializers

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ('title', 'content', 'is_featured') # if not declared, all fields of the model will be shown

In this tutorial we have used ModelSerializer, more on this.

4. Views

A view function, or view for short, is a Python function that takes a Web request and returns a Web response.

# posts/api/views.py

from ..models import Post
from . import serializers
from rest_framework import generics, status
from rest_framework.response import Response

class PostListView(generics.ListAPIView):
    queryset = Post.objects.all()
    serializer_class = serializers.PostSerializer

As seen above, ListAPIView is used for read-only endpoints to represent a collection of model instances.

In this code snippet, we use generics view methods from the rest_framework, more on this.

5. URLs

This is where we setup our routes or URL paths to our designated views in which we expect specific responses for each.

# posts/urls.py

from django.urls import path
from . import views
from .api import views

urlpatterns = [
    path('', views.PostListView.as_view(), name=None)
]

6. Finalizing Setup

Ensure that the rest_framework is added to our project's apps.

# djangoapi/blog/blog/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', # Add it here
    'posts'
]

7. Django Admin

Since we haven't setup our POST requests yet, we will be populating the database through django's admin panel.

To do that, create a superuser account admin with password 1234password.

$ python manage.py createsuperuser --email [email protected] --username admin

Password:
Password (again):
This password is too common.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.

Register the model in the admin panel.

# posts/admin.py

from django.contrib import admin
from .models import Post

# Register your models here.
admin.site.register(Post)

That's it. Visit the admin panel and update posts model's records. More on this.

8. Testing our API

$ python manage.py runserver
GET /api/v1/posts/
HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

[
    {
        "title": "Example Post #1",
        "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        "is_featured": false
    },
    {
        "title": "Example Post #2",
        "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        "is_featured": true
    }
]

Great. Now it's time for us to update our views and finish the standard CRUD operations.

9. Adding more views

POST is a method used for creating (sometimes updating) a resource in the database.

# posts/api/views.py

from ..models import Post
from . import serializers
from rest_framework import generics, status
from rest_framework.response import Response

class PostCreateView(generics.CreateAPIView):
    queryset = Post.objects.all()
    serializer_class = serializers.PostSerializer

    def create(self, request, *args, **kwargs):
        super(PostCreateView, self).create(request, args, kwargs)
        response = {"status_code": status.HTTP_200_OK,
                    "message": "Successfully created",
                    "result": request.data}
        return Response(response)

Most often, we separate List and Create view classes when we want to expose a list of data set while easily preventing a certain request to POST or create a resource in the database for that specific List view.

Usecase always varies for apps, you are opt to use ListCreateAPIView or even ViewSets for combining the logic for a set of related views.

Optional: Since we want to display the data in a more systematic way, we override create method and map our inline custom response handler.

Adding more views with methods GET, PATCH, DELETE to handle a specific blog post detail.

class PostDetailView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = serializers.PostSerializer

    def retrieve(self, request, *args, **kwargs):
        super(PostDetailView, self).retrieve(request, args, kwargs)
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        data = serializer.data
        response = {"status_code": status.HTTP_200_OK,
                    "message": "Successfully retrieved",
                    "result": data}
        return Response(response)

    def patch(self, request, *args, **kwargs):
        super(PostDetailView, self).patch(request, args, kwargs)
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        data = serializer.data
        response = {"status_code": status.HTTP_200_OK,
                    "message": "Successfully updated",
                    "result": data}
        return Response(response)

    def delete(self, request, *args, **kwargs):
        super(PostDetailView, self).delete(request, args, kwargs)
        response = {"status_code": status.HTTP_200_OK,
                    "message": "Successfully deleted"}
        return Response(response)

10. Updating URLs

# posts/urls.py

from django.urls import path
from . import views
from .api import views

urlpatterns = [
    path('', views.PostListView.as_view(), name=None),
    path('create/', views.PostCreateView.as_view(), name=None),
    path('<int:pk>/', views.PostDetailView.as_view(), name=None)
]

Now you can send requests to your API via Postman, your app or do a GETrequests from your browser, examples:

POST /api/v1/posts/create/
HTTP 200 OK
Allow: POST, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "status_code": 200,
    "message": "Successfully created",
    "result": {
        "csrfmiddlewaretoken": "rnSUN3XOIghnXA0yKghnQgxg0do39xhorYene5ALw3gWGThK5MjG6YjL8VUb7v2h",
        "title": "Creating a resource",
        "content": "Howdy mate!"
    }
}
GET /api/v1/posts/1/
HTTP 200 OK
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "status_code": 200,
    "message": "Successfully retrieved",
    "result": {
        "title": "Sample Post",
        "content": "Sample Post Content",
        "is_featured": false
    }
}

That's it. You have successfully managed to develop RESTful APIs with DRF! Cheers!

Source code

Available on GitHub.