DJANGO Multi-Authentication PowerUser Guide (Python)

Last updated: December 09, 2025
Author: Paul Namalomba
- SESKA Computational Engineer
- Software Developer
- PhD Candidate (Civil Engineering Spec. Computational and Applied Mechanics)

Contact: kabwenzenamalomba@gmail.com
Website: paulnamalomba.github.io

Framework License: MIT

Overview

This guide focuses on implementing robust multi-authentication in Django using Python: combining username/password, OAuth2/OIDC (Google, Azure AD, GitHub), JWT for APIs, and optional SAML and MFA. It shows how to structure settings, authentication backends, URL routing, and views so that you can safely support multiple identity providers in one project.

Contents


Quickstart

Install and Scaffold Project

python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate

pip install "Django>=4.2,<5" djangorestframework

django-admin startproject config .
python manage.py startapp accounts

Core Dependencies

For multi-auth, you commonly combine:

pip install \
  django-allauth \
  djangorestframework-simplejwt \
  django-otp django-otp-yubikey \
  python3-saml

Key Concepts

Authentication Backends

In settings.py:

AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",           # Username/password
    "allauth.account.auth_backends.AuthenticationBackend",  # Social auth (Google, GitHub, etc.)
]

Session vs Token Auth


Configuration and Best Practices

Settings Layout

A typical production layout:

# config/settings/base.py
from pathlib import Path
import os

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = os.environ.get("DJANGO_DEBUG", "False") == "True"

ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",")

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

    # Third-party
    "rest_framework",
    "rest_framework_simplejwt",
    "allauth",
    "allauth.account",
    "allauth.socialaccount",
    "allauth.socialaccount.providers.google",

    "otp",
    "otp_totp",

    # Local
    "accounts",
]

REST framework and JWT:

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework_simplejwt.authentication.JWTAuthentication",
        "rest_framework.authentication.SessionAuthentication",
    ],
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticated",
    ],
}

from datetime import timedelta

SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=15),
    "REFRESH_TOKEN_LIFETIME": timedelta(days=30),
    "ROTATE_REFRESH_TOKENS": True,
    "BLACKLIST_AFTER_ROTATION": True,
}

Environment Variables

export DJANGO_SECRET_KEY="<long-random-secret>"
export DJANGO_DEBUG="False"
export DJANGO_ALLOWED_HOSTS="localhost,api.example.com"

# Google OAuth2
export SOCIAL_AUTH_GOOGLE_CLIENT_ID="<client-id>"
export SOCIAL_AUTH_GOOGLE_CLIENT_SECRET="<client-secret>"

# Azure AD
export SOCIAL_AUTH_AZUREAD_TENANT_ID="<tenant-id>"
export SOCIAL_AUTH_AZUREAD_CLIENT_ID="<client-id>"
export SOCIAL_AUTH_AZUREAD_CLIENT_SECRET="<client-secret>"

# JWT
export JWT_SIGNING_KEY="<jwt-signing-key>"

Security Considerations

Password Storage and MFA

Example: enabling Argon2:

PASSWORD_HASHERS = [
    "django.contrib.auth.hashers.Argon2PasswordHasher",
    "django.contrib.auth.hashers.PBKDF2PasswordHasher",
]

Token Handling


Examples

Example 1: Base Auth Setup

accounts/models.py:

from django.contrib.auth.models import AbstractUser
from django.db import models


class User(AbstractUser):
    # Example extension: tenant field
    tenant_id = models.CharField(max_length=64, blank=True, null=True)

config/settings/base.py:

AUTH_USER_MODEL = "accounts.User"
LOGIN_REDIRECT_URL = "/"
LOGOUT_REDIRECT_URL = "/"

URLs:

# config/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("accounts/", include("allauth.urls")),
    path("api/auth/", include("accounts.api_urls")),
]

Example 2: OAuth2/OIDC with Social Login

config/settings/base.py (allauth configuration):

SITE_ID = 1

ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_AUTHENTICATION_METHOD = "username_email"

SOCIALACCOUNT_PROVIDERS = {
    "google": {
        "APP": {
            "client_id": os.environ.get("SOCIAL_AUTH_GOOGLE_CLIENT_ID"),
            "secret": os.environ.get("SOCIAL_AUTH_GOOGLE_CLIENT_SECRET"),
            "key": "",
        }
    }
}

Template snippet for login options:

<a href="{% provider_login_url 'google' %}">Sign in with Google</a>

Example 3: JWT for APIs

accounts/api_urls.py:

from django.urls import path
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
    path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
]

Example API view:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated


class ProfileView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        user = request.user
        return Response({
            "username": user.username,
            "email": user.email,
            "auth": request.auth.__class__.__name__ if request.auth else "session",
        })

Example 4: MFA with django-otp

config/settings/base.py:

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",

    "otp.middleware.OTPMiddleware",
]

Simple view enforcing OTP:

from django_otp.decorators import otp_required
from django.http import HttpResponse


@otp_required
def sensitive_view(request):
    return HttpResponse("You passed MFA and can access this sensitive view.")

Troubleshooting

Common Configuration Errors

Debugging Tips


Performance and Tuning

Scaling Auth-heavy APIs


References and Further Reading