Django authentication with LinkedIn

Social Authentication (or Social Login) is a method of simplifying end-user logins by utilizing current login information from prominent social networking services like Facebook, Twitter, Google, and LinkedIn, which is the subject of this article. Most websites require users to log in using social login platforms for a better authentication/registration experience instead of establishing their systems.

Django Allauth is a tool for quickly setting up an authentication/registration system that works with various frameworks and authentication providers.

Many big websites now allow visitors to log in using their Facebook, Google, or LinkedIn accounts. I’ll confess that most users do so because it is simple, especially now that everyone has those accounts, and they are much more trusted.

Django authentication with LinkedIn

This article will show you how to incorporate the Django Allauth library into your Django project and use OAuth 2.0 to offer user authentication using LinkedIn.

What is OAuth 2.0, and how does it work?

OAuth 2.0 is an authorization system that allows apps to access a user’s account to authenticate or register with popular social networking sites. The end-user has control over which information the program has access to. It focuses on streamlining the development process while also offering particular authorization routines for web and desktop applications, mobile phones, and Internet of Things (IoT) devices.

How does Oauth2 Work?

The creation of OAuth2 was initially to have it as a protocol that facilitates web authentication. This kind of protocol expects some HTML rendering and capabilities to handle browser redirection. That makes it different from the usual protocol for network authentication.

In that regard, JSON-based application programming interface comes at a disadvantage when dealing with a web authentication protocol. As a result, this calls for the use of a workaround to get it working correctly. However, in this article, we concentrate on a typical server-side web application.

OAuth 2 Server Flow

The initial step happens entirely outside of the primary application, where the project owner does the registration of every OAuth2 app provider they need their logins. For instance, the registration of a LinkedIn application since LinkedIn can provide logins on their behalf.

During the registration process, the owner provides the Oauth2 provider with a callback URI. The callback URI is a designation in the owner’s application where requests from the provider will be received. The provider, in turn, makes available both a client key and a client secret which gives the owner the necessary authentication because any authentication requires these access tokens to verify any login attempts.

When the application creates a page with a designated button indicating login through a specific social media application, it indicates the beginning of the flow. For instance, the application may have a button such as “Login with LinkedIn”. Such buttons are essentially links pointing to URLs that have similarities with the following sample.

https://oauth2provider.com/auth?response_type=code&client_id=CLIENT_KEY&redirect_uri=CALLBACK_URI&scope=profile&scope=email

The above code indicates that the owner has provided a response type, a Client Id, and a redirect URI. However, the owner did not provide any secret keys. In addition, the owner has requested permission to access the user’s email address and profile from the Oauth2 provider’s server. These scopes check the permissions given to you from the user and the authorization of the provided access token.

Upon receiving the access token, the browser belonging to the user is then redirected to a page (usually dynamic) that the respective Oauth2 provider ultimately controls. The Oauth2 provider, at this point, has the sole responsibility of checking and confirming that the client keys and the callback URI are the same. The flow will then diverge based on the session tokens of the given user.

For example, if the user is not already logged in to the given service, they will be prompted to log in. On the other hand, if the user is already logged in to the service, the user will be requested to give permissions that allow your program to log in.

Once the user has given the necessary permissions, the Oauth2 server will forward them to the callback URI earlier. However, this time, it will also have an authorization code as part of the query parameters, as shown below.

GET https://api.yourapp.com/oauth2/callback/?code=AUTH_CODE

The authorization code expires quickly since it is meant for one-time use only. Thus, upon receipt of the authorization code, a new request is issued to the Oauth2 provider from your server. The latter will contain both the client secret and the authorization code. Here is an example,

POST https://oauth2provider.com/token/?
grant_type=authorization_code&
code=AUTH_CODE&
redirect_uri=CALLBACK_URI&
client_id=CLIENT_KEY&
client_secret=CLIENT_SECRET

The POST request shown above is authenticated using the authorization code provided. Nevertheless, this happens through the user’s system, depending on the kind of flow employed here. If this is the case, then the entire process is also risky.

The limitations of the authorization code include rapid expiry. That causes it to be used once in place, reducing the risk of using an untrusted system to send authentication credentials.

This call from the owner’s server to the respective Oauth2 providers’ server is one of the key constituents of the login process of the Oauth2 server-side. Now, controlling the call gives assurances of it being TLS-secured and keeping it resistant to assaults coming from wiretapping.

Further, we can only ascertain users’ explicit consent through the provision of the authorization code. At a further level, the client secret is provided to ensure that the whole request did not result from a computer virus or a spyware program that intercepted the authorization code to the user’s system.

When everything checks out, the server returns an access token, which can then be used to make calls to the Oauth2 provider while logged in as the user.

Subsequently, the owners’ server will redirect the browser belonging to a given user to the designated landing page for successfully logged-in users upon receiving access token from the server to do whatever brought them to the site.

Often, the access token is stored in the user’s server-side session cache allowing the server to continue making calls to the registered Oauth2 provider when the need arises. For example, Google has a refresh token that extends your access token’s duration. On the other hand, Facebook provides an endpoint for you to exchange your short-lived access token with longer-lived tokens.

Setting up this flow in the case of REST API is a complicated process. Even though you can have the backend providing a callback URL while at the same time having a login page on the frontend, problems will mar the process. You may receive an access token to send the visitor to the landing page, but there is no definite RESTful way to achieve this.

What we will cover:

  • Setup a Django project
  • Linkedin Authentication

Authentication with LinkedIn

  • Create a Linkedin App
  • Update Django Settings.py
  • Common Errors.

Creating a LinkedIn Application

Step 1: mkdir django-linkedin-auth && cd django-linkedin-auth

Step 2: virtualenv linkedin_env

create a virtual environment
create a virtual environment

Step 3: source linkedin_env/bin/activate

activate virtual environment
activate virtual environment

Step 4: pip install Django==3.2.6

install Django using pip
install Django using pip


Step 5: django-admin startproject LinkedInLogin_app

startproject LinkedInLogin_app using django-admin
startproject LinkedInLogin_app using django-admin


Step 6: python manage.py migrate


Step 7: python manage.py runserver

python manage.py runserver
python manage.py runserver

Authentication Keys for LinkedIn

We need certain app-specific credentials to distinguish our app’s login from other social logins on the web to recognize the LinkedIn social login that we deployed. To use social service providers like Linkedin for authentication, you’ll need a Client ID and a Secret Key, which you can receive by creating a Linkedin app. First, go to the Linkedin developer portal and create an app, as shown below.

create LinkedIn app
create LinkedIn app

If you already have a Linkedin page, you can add it or start a new page by clicking on the + before “Create a new LinkedIn Page”.

Create a LinkedIn Page
Create a LinkedIn Page

Then, we will be starting a new Linkedin page by picking the small company page option.

fill in the needed details about your business
fill in the needed details about your business

After you’ve created a page, you may add it; after that, you’ll need a logo, and your app will be complete. In the Auth tab, check your Key and Secret Key, then in the product tab, click Sign in with Linkedin.

Add a Redirect URL to the Auth tab as follows.

application credentials & redirect URLs
application credentials & redirect URLs
http://127.0.0.1:8000/accounts/linkedin_oauth2/login/callback/

Update Django Settings.py

First, we will update the social provider in the installed apps.

INSTALLED_APPS = [
….
#social account providers
'allauth.socialaccount.providers.linkedin_oauth2',
]

In addition, we will also add Linkedin as a social service provider in the settings.py as shown below.

# Linkedin Authentication Setting

SOCIALACCOUNT_PROVIDERS = {
'linkedin': {
'SCOPE': [
'r_basicprofile',
'r_emailaddress'
],
'PROFILE_FIELDS': [
'id',
'first-name',
'last-name',
'email-address',
'picture-url',
'public-profile-url',
]
}
}

SCOPE refers to the rights you must supply; r_basicprofile is necessary to read the basic profile, and r_emailaddress is required to check the user’s email address.

The information returned after a successful login is stored in PROFILE_FIELDS.

Now we need to go into Django Admin and add our Key and Secret Key.

Creating superuser credentials

For our Django project, we’ll start by creating a superuser.

To create a superuser for our LinkedInLogin_app application, we will run the following command and pass in the required details, including the username, email address, a designated password, and a confirmation for the same.

python manage.py migrate --run-syncdb 
python manage.py createsuperuser
python manage.py createsuperuser
python manage.py createsuperuser

Django – Allauth

With Django AllAuth, you can create user signup, login, and logout, as well as other features like email change, lost password, and so on, in just 10 minutes.

Installing Django Allauth

pip install django-allauth
install django-allauth using pip
install django-allauth using pip

We will then modify the INSTALLED_APPS in the settings.py by adding allauth apps. If we do not do this, Django Allauth will not work properly with our LinkedInLogin_app. So, we will add the following under LinkedInLogin_app. The new changes should appear as follows.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # custom apps
    'LinkedInLogin_app',

    # alluth
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    
    # linkedin
    'allauth.socialaccount.providers.linkedin_oauth2',
]

SITE_ID = 1

# allout login settings

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'


ACCOUNT_EMAIL_VERIFICATION = "none"
LOGIN_REDIRECT_URL = "home"
ACCOUNT_LOGOUT_ON_GET = True

The above section defines several parameters that we will define below.

Allauth defines the authentication backend that ensures that all the login and logout through the regular username and password or OAuth is now handled by Allauth.

ACCOUNT_EMAIL_VERIFICATION =” none” is responsible for turning off email verification because it is unnecessary now. It is because Django does a workflow setup for its email verification.

LOGIN_REDIRECT_URL=” home” is responsible for directing the user to the homepage after successful authentication.

ACCOUNT_LOGOUT_ON_GET=True ensures that the confirm logout page is skipped by directly logging out the user when the logout button is clicked through a GET request.

SITE_ID – is needed for Django Allauth to work properly.

Initially, we added the Django “sites” framework to enable Allauth to work as expected. Then we added the primary Allauth apps that comprise allauth, allauth.account, and allauth.socialaccount.

After that, we will add the following set of code at the bottom of the settings.py

AUTHENTICATION_BACKENDS = (
"allauth.account.auth_backends.AuthenticationBackend",
)

That will include Django Allauth. In addition, we need to apply the migrations associated with Django Allauth by running the following command.

python manage.py migrate
 Django Allauth Migrations
Django Allauth Migrations

Ensure not to forget the above step because Django Allauth needs the new tables.

Update Urls

We will also add allauth paths in LinkedInLogin_app/Urls.py as follows.

# LinkedInLogin_app/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('allauth.urls')),
]

Subsequently, we will create and apply migrations by running these commands.

python manage.py makemigrations

python manage.py migrate
make and run migrations
make and run migrations

Finally, we will start the server by running the following set of commands as follows.

Python manage.py runserver

Then, we will access the following URL http://127.0.0.1:8000/accounts/. That shows all the features that are available for you to access now. Also, note the following URL as being very vital in your operations as indicated.

  • To signup go to http://127.0.0.1:8000/accounts/signup/
  • To log in, go to http://127.0.0.1:8000/accounts/login/
  • To log out, go to http://127.0.0.1:8000/accounts/logout/
  features available at accounts
features available at accounts

We are not done with setting up the Django Allauth and doing the configurations as well. Now, we are super ready to test. You should be able to run the application and log in using a username and a password. In our case, we will use the credentials we created in the section above while examining superuser credentials. To access the login page, you can access the following link, http://127.0.0.1:8000/accounts/login/

Note that you can also make changes to the CSS stylesheets to make your application more appealing to the user. In addition, you may also choose to make changes to the default templates.

admin dashboard

So, we will need to access the admin panel by using the link, http://127.0.0.1:8000/admin, and log in with the same credentials you used to create a superuser. As a result, you should have a page similar to this after you login successfully.

LinkedInLogin administrator page
LinkedInLogin administrator page

add a new site

Finally, we will update the home page template as shown below.

We will start by adding a site to our Django application under the category SITES. Now select Add in Sites from the drop-down menu. Update Domain name 127.0.0.1 and any other name (LinkedIn) for this domain because we are operating it on our local system.

add a site
add a site

Also, look for SITE_ID in the URL of this page. In our case, the URL is http://127.0.0.1:8000/admin/sites/site/2/change/. Therefore we adjusted SITE_ID=2 in the settings.py file.

update site using site_id
update site using site_id

Add Django as a new social application

Linkedin must now be added to the Admin Social Applications. Fill in the details as given below, and select 127.0.0.1 from the list of sites.

First, click on “Add Social Application” and fill the empty fields under the social applications.

  • Select LinkedIn as the Provider
  • Add a Customized name
  • Add the Client ID and Client secret you noted from the previous section.
  • Then, add 127.0.0.1 as one of the selected sites.

After filling all the sections, it should appear as in the diagram below.

add LinkedIn as a new social application
add LinkedIn as a new social application

Now, ensure that everything is OK by running makemigrations and migrate as follows since that’s all we need.

python manage.py makemigrations
python manage.py migrate

Then, we will run the server using the command, python manage.py runserver, and visit the Login URL.

log in page
log in page

Typical Errors

  • Unauthorized scope r_liteprofile, discovered

If you receive a No Redirect URL or Mismatch of Redirect URL error, click on Linkedin in the signup process and copy the URL. Then use an online decoder to decode and check your redirect URL to see what redirect URL Linkedin is using.

If you receive a No Redirect URL or Mismatch of Redirect URL error, click on Linkedin in the signup process, copy the URL. Then use an online decoder to decode and check your redirect URL to see what redirect URL Linkedin is using.

If you’re getting an Unauthorized scope r_liteprofile problem, you likely neglected to pick Sign in with Linkedin in the Products page of your App dashboard.

Creation of Templates

In this section, we will create templates for our application to help communicate the process happening in the application.

First, we will create a “templates” directory in the base directory and two other files, namely, home.html and base.html.

mkdir templates && cd templates
touch base.html home.html

We will then update the path of the template on the settings.py to make it easier for Django to find the templates. The new changes should appear as follows.

# LinkedInLogin_app/settings.py

TEMPLATES = [
{
…
"DIRS": [str(BASE_DIR.joinpath("templates"))],
…
},
]

Then also make the following changes to the two template files as follows.

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

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css"
      rel="stylesheet"
    />
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
    />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Django LinkedIn Login</title>
  </head>
  <body>
    {%% block content %%} {%% endblock content %%}
  </body>
</html>

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

{%% extends 'base.html' %%} {%% load socialaccount %%}

{%% block content %%}

<div class="container" style="text-align: center; padding-top: 10%%;">
  
  <h1>Django LinkedIn Login</h1>

  <br /><br />

  {%% if user.is_authenticated %%}
    <h3>Welcome {{ user.username }} !!!</h3>
    <br /><br />
    <a href="{%% url 'account_logout' %%}" class="btn btn-danger">Logout</a>
  {%% endif %%}
  
</div>

{%% endblock content %%}

Next, we will create a view to serve the templates we have created above. Inside the view.py file, add the following code.

# LinkedInLogin_app/views.py

from django.views.generic import TemplateView


class Home(TemplateView):
    template_name = "home.html"

We will then create a new URL so that the final look of the urls.py is shown below.

# LinkedInLogin_app/urls.py

from django.contrib import admin
from django.urls import path, include

from .views import Home # new

urlpatterns = [
path("admin/", admin.site.urls),
path("accounts/", include("allauth.urls")),
path("", Home.as_view(), name="home"), # new
]

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

{%% extends 'base.html' %%} {%% load socialaccount %%}

{%% block content %%}

<div class="container" style="text-align: center; padding-top: 10%%;">
  <h1>Django Authentication with LinkedIn</h1>

  <br /><br />

  {%% if user.is_authenticated %%}
    <h3>Welcome {{ user.username }} !!!</h3>
    <br /><br />
    <a href="{%% url 'account_logout' %%}" class="btn btn-danger">Logout</a>
  {%% else %%}
    <!-- LinkedIn button code starts here -->
    <a href="{%% provider_login_url 'linkedin' %%}" class="btn btn-secondary">
      <i class="fa fa-linkedin fa-fw"></i>
      <span>Login with LinkedIn</span>
    </a>
    <!-- LinkedIn button code ends here -->
  {%% endif %%}
</div>

{%% endblock content %%}

Now, we are ready to run and log in via LinkedIn.

Login with LinkedIn

We first need to access the homepage for our Django authentication application by using the following address http://127.0.0.1:8000. If you are OK, you should be able to see a page similar to this.

Sign in with LinkedIn
Sign in with LinkedIn

After clicking on the button “Login with LinkedIn”, you will be redirected to LinkedIn’s Login page with a clear indication that you need to sign in to LinkedIn to proceed to LinkedInLogin_app is our Django application. When successfully signed in, you should see a page similar to this.

Successfully logged in with LinkedIn
Successfully logged in with LinkedIn

Complete Source Code for reference

# LinkedInLogin_app/settings.py

"""
Django settings for LinkedInLogin_app project.

Generated by 'django-admin startproject' using Django 3.2.6.

For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-%%@m@&@70-9=4(^yl4zckk=l_a)a7hr1c^0*!x-6tddjs1w#4p('

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'LinkedInLogin_app',

    # alluth
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',

    # linkedin
    'allauth.socialaccount.providers.linkedin_oauth2',
]

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',
]

ROOT_URLCONF = 'LinkedInLogin_app.urls'

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',
            ],
        },
    },
]

WSGI_APPLICATION = 'LinkedInLogin_app.wsgi.application'


# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/

STATIC_URL = '/static/'

# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

SITE_ID = 2

# allout login settings

LOGIN_URL = '/accounts/login'
LOGIN_REDIRECT_URL = '/home/'
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
ACCOUNT_EMAIL_VERIFICATION = "none"
ACCOUNT_LOGOUT_ON_GET = True

AUTHENTICATION_BACKENDS = (
    "allauth.account.auth_backends.AuthenticationBackend",
)
# LinkedInLogin_app/urls.py

from django.contrib import admin
from django.urls import path, include

from .views import Home # new

urlpatterns = [
path("admin/", admin.site.urls),
path("accounts/", include("allauth.urls")),
path("", Home.as_view(), name="home"), # new
]
# LinkedInLogin_app/views.py

from django.views.generic import TemplateView


class Home(TemplateView):
    template_name = "home.html"
<!-- # LinkedInLogin_app/home.html -->

{%% extends 'base.html' %%} {%% load socialaccount %%}

{%% block content %%}
<div class="container" style="text-align: center; padding-top: 10%%;">
  <h1>Django Authentication with LinkedIn</h1>

  <br /><br />

  {%% if user.is_authenticated %%}
    <h3>Welcome {{ user.username }} !!!</h3>
    <br /><br />
    <a href="{%% url 'account_logout' %%}" class="btn btn-danger">Logout</a>
  {%% else %%}
    <!-- LinkedIn button code starts here -->
    <a href="{%% provider_login_url 'linkedin_oauth2' %%}" class="btn btn-secondary">
      <i class="fa fa-linkedin fa-fw"></i>
      <span>Login with LinkedIn</span>
    </a>
    <!-- LinkedIn button code ends here -->
  {%% endif %%}
</div>

{%% endblock content %%}

<!-- # LinkedInLogin_app/base.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css"
      rel="stylesheet"
    />
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
    />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Django LinkedIn Authentication</title>
  </head>
<body>
  {%% block content %%} {%% endblock content %%}
</body>
</html>

Conclusion

Most social account authentication providers function the same way; you’ll need to build an app or project on their developer page to obtain a client key and client secret key, then update settings.py and add it to the Admin social application.

Django AllAuth is extremely easy to incorporate into your Django project. It includes user registration, login, logout, email update, forgotten password, and many other features already developed and ready to use. Django AllAuth also validates data.

Check out the Django AllAuth Doc for further information.

The most popular and trusted social accounts are Facebook, GitHub, LinkedIn, and Google, but if you wish to add additional, Click here for a list of all supported social accounts.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *