Django application with allauth configuration.
Tags: django, allauth, sso, tutorial
Published: 2024-07-08 06:22:35

Hi, dev ninjas🥷!

Welcome to my first post! Today, we're diving into Django's built-in authentication system, specifically using the django-allauth package. This powerful tool provides a range of authentication providers, simplifying the workflow for managing logins.

In this tutorial, we'll explore how to add Single Sign-On (SSO) login functionality to an existing Django application. Note that in this application, user sign-up and creation are disabled. Instead, users will be pre-added to the system, and they will link their social accounts through the Django application.

Let's walk through this process step-by-step and see how it can be done.

---

## Setting Up the Django Project

Before we dive into the SSO integration, let's review the setup process for a Django project. If you already have an existing Django application, you can skip ahead to the configuration steps. For those new to Django, here’s a quick guide on creating a project and app, installing necessary packages, and configuring settings.

### Step 1: Create a New Django Project
For those new to Django, we'll quickly cover how to create a new Django project and app. If you already have an existing project, you can skip to the next step.

Creating a Django Project and App:

Open your terminal and run the following commands:

```
$ django-admin startproject myproject
$ cd myproject
$ python manage.py startapp myapp
```

This will create a new Django project named `myproject` and an app named `myapp` (you can have your custom name for both).

### Step 2: Install and Configure django-allauth
Next, we need to install django-allauth. This package provides a comprehensive authentication system with support for multiple authentication providers.

#### Managing Dependencies:

- **Using Docker:**
If you are using Docker for your development environment, you can 
define your dependencies in the `Dockerfile` and `docker-compose.yml` 
files. This approach ensures that all dependencies are consistently managed across different environments.

- **Using Virtual Environment:**
If you are not using Docker, it's recommended to create a virtual environment to manage your project’s dependencies:
   
*Create and Activate a Virtual Environment:*
First, make sure to create a virtual environment:

```
$ python -m venv venv
$ source venv/bin/activate  # On Windows use `venv\Scripts\activate`
```

Install django-allauth:

Add django-allauth to your requirements.txt file:
```
Django>=3.0,<4.0
django-allauth
authlib==1.0.0
```
Then, install the dependencies: `pip install -r requirements.txt`
If you are not using a requirements.txt file, you can install django-allauth directly: `pip install django-allauth` (inside virtual env.)

Update `settings.py`:
Open your `settings.py` file and add allauth and its dependencies to the INSTALLED_APPS list. Also, include the required authentication backends and set up the site ID:

```
# config/settings.py

INSTALLED_APPS = [
    ...
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'allauth.socialaccount.providers.okta', # for sso config
    'allauth.socialaccount.providers.google',  # Include other providers as needed like instead of google, make it github, etc.
    'allauth.socialaccount.providers.github', # example
    ...
]

SITE_ID = 1 #default one, if you're adding in the admin site, change it to the respective one

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend', # default backend
    'allauth.account.auth_backends.AuthenticationBackend', #backend for OAuth
)

LOGIN_REDIRECT_URL = '/' # redirects to the login page on authentication
# For OAuth_AUTHENTICATION

ACCOUNT_EMAIL_VERIFICATION = "none"
ACCOUNT_EMAIL_REQUIRED = True
SOCIALACCOUNT_QUERY_EMAIL = True
SOCIALACCOUNT_AUTO_SIGNUP = False #new users will not get signed up using any of the providers, only if the user with the same mail id as in the provider then the user gets signed in.
SOCIALACCOUNT_LOGIN_ON_GET=True

SOCIALACCOUNT_ADAPTER = 'config.adapters.MySocialAccountAdapter' 
SOCIAL_AUTH_REDIRECT_IS_HTTPS = True

SOCIALACCOUNT_PROVIDERS = {
    'google': {
        'APP': {
            'client_id': 'YOUR_CLIENT_ID',
            'secret': 'YOUR_SECRET',
            'key': ''
        }
    },
    ..... # same way for other providers, for okta it is separate
}

# For Auth0 - Okta(SSO)

AUTH0_CLIENT_ID = 'YOUR_CLIENT_ID'
AUTH0_CLIENT_SECRET = 'YOUR_SECRET'
AUTH0_DOMAIN = 'YOUR_DOMAIN'
AUTH0_CALLBACK_URL = 'http://localhost:8000/callback'

# these configurations can either be given here, or in the admin page.
```

For getting client id and other credentials of any adapter, create an app in the respective developer area of the provider.

For E.g..: Google:

- Go to Google Cloud 
- Create a project
- Go to API & Services
- Go to Oauth consent screen and complete the consent form
- Create an app
- Now come to credentials tab
- There go to the respective app and find out the client id and secret key
- If secret key is not there, create one!


![redirect URIs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v5f2g62r23an503rrvet.png)


Similarly create for other providers, and for Auth0 refer the references section given below.

### Step 3: Create adapters and manage authentications
Now we have to create a custom adapters.py file for configuring pre-social-login and connecting the socialaccounts part.

```
# config/adapters.py
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from django.contrib.auth.models import User
from app.users.models import User
from django.core.exceptions import PermissionDenied
from django.shortcuts import redirect
from django.contrib import messages

class MySocialAccountAdapter(DefaultSocialAccountAdapter):
    def is_open_for_signup(self, request, sociallogin):
        return False #this is in case if you don't want auto login or signup
    
    def pre_social_login(self, request, sociallogin):
        if sociallogin.is_existing:
            return

        user_email = sociallogin.account.extra_data.get('email')
        user_mail = sociallogin.account.extra_data.get('mail') # sometimes it can be mail or email in the data, so will have to check both
        if user_email or user_mail:
            try:
                path = "/accounts/{username}/"
                # Attempt to retrieve the user by email
                if user_email:
                    user = User.objects.get(email=user_email)
                elif user_mail:
                    user = User.objects.get(email=user_mail)
                # Connect the social account to the existing user
                sociallogin.connect(request, user)
                return
            except User.DoesNotExist:
                # User does not exist, proceed with the redirection and message
                # messages.warning(request, "User account does not exist.")
                return redirect('/login')

# this will make sure the socialaccount is getting connected
```
During this process, as said earlier, an existing user while trying to login using one of the providers with the same mail id will be able to login.


### Step 4: Customizing URL(s) and Views
```
path('login/', users.user_login, name='custom_login'), # existing login page
path('logout/', users.user_logout, name='logout'),
path('accounts/social/connections/', users.social_connections, name='socialaccount_connections'), #custom view url for managing connected accounts and other stuffs
path('accounts/social/signup/', base.signup_view, name='socialaccount_signup'),
path('accounts/', include('allauth.urls'), name='account'), #this includes all the urls for the social accounts
path('sso_login/', base.auth0_login, name='okta_login'),
path("callback/", base.auth0_callback, name="sso_callback"),
```
If any url is not required from the default url(s) from the accounts, we can list only those urls, just like `accounts/social/connections/` this path and remove `accounts/', include('allauth.urls')` this path.

`social_connections view`


```
def social_connections(request):
    if request.method == 'POST':
        account_id = request.POST.get('account')
        if account_id:
            try:
                account = SocialAccount.objects.get(pk=account_id, user=request.user)
                account.delete()
                messages.success(request, 'Social account removed successfully.')
            except SocialAccount.DoesNotExist:
                messages.error(request, 'Social account not found.')
        else:
            messages.error(request, 'No social account selected.')

        return redirect('socialaccount_connections')  # Adjust this to the appropriate URL name for your profile page

    social_accounts = SocialAccount.objects.filter(user=request.user)
    return render(request, 'admin/project/social_accounts.html', {'social_accounts': social_accounts})
```

this view mainly focuses on showing the connected accounts from different social accounts. For example, if the user has logged in using google, then in this page, it shows only google provider, all other providers will get added once the login happens through the provider.

### Step 5: Login for social accounts

```
<a href="{% url 'okta_login' %}" class="okta btn" style="color: black;">Okta</a><br>          
<a href="{% provider_login_url 'google' process='login' method='js_sdk' %}" class="google btn" style="color: black;">Google</a><br>
```

same way for other providers!

---

## Running and testing 

After setting up django-allauth and configuring your project, it’s important to test the integration to ensure everything works as expected.

### Testing Locally
- Run the Development Server:
Start your Django development server to test the application.
```
python manage.py runserver
```
- Access the Login Page:
Navigate to the login page, and customize the login page to have sso options with above mentioned urls.

![Login page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kn603n8ee0ffymq5ajgr.png)


![sso options](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bt276sdw114cykr0smum.png)

- Test Different Providers:
Try logging in using different social providers you configured (e.g., Google, GitHub). Ensure that users with existing emails in your database can log in and their social accounts are linked correctly.

### Testing using docker
1. `docker compose up` your application
2. Go to login page and test your application with the same way!

---

## Conclusion
In this tutorial, we have covered how to integrate django-allauth into a Django application to provide SSO login functionality. We discussed:

- Setting up a Django project and installing dependencies.
- Configuring django-allauth in settings.py.
- Creating custom adapters to manage the social login process.
- Defining URLs and views for login, logout, and social account connections.
- Testing the integration locally and in production.

By following these steps, you can streamline the authentication process for your users, providing a seamless login experience with various social providers.

---
## Additional Resources

- [django-allauth Documentation](https://docs.allauth.org/en/latest/)
- [Django Documentation](https://docs.djangoproject.com/en/5.0/)
- [Auth0 Documentation](https://auth0.com/docs/quickstart/webapp/django/interactive)


> _Any issues can be addressed in the comments section below! Feel free to ask if you have any questions or need further clarification on any part of this tutorial. Happy coding!_