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
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.
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
For multi-auth, you commonly combine:
pip install \
django-allauth \
djangorestframework-simplejwt \
django-otp django-otp-yubikey \
python3-saml
In settings.py:
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend", # Username/password
"allauth.account.auth_backends.AuthenticationBackend", # Social auth (Google, GitHub, etc.)
]
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,
}
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>"
PBKDF2 by default) or stronger algorithms (Argon2).django-otp or external IdPs).Example: enabling Argon2:
PASSWORD_HASHERS = [
"django.contrib.auth.hashers.Argon2PasswordHasher",
"django.contrib.auth.hashers.PBKDF2PasswordHasher",
]
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")),
]
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>
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",
})
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.")
aud) and issuer (iss) claims, and that signing keys match.python -m http.server or tools like httpie to simulate callback requests.