Single sign-on (SSO) lets a user log in once with one identity provider and reach every app that trusts it — no separate password per application. Auth0 is a hosted identity provider that handles login, multi-factor authentication, social logins, and enterprise federation for you. To wire Auth0 into a Django app you do not build a custom protocol: Auth0 is a standard OpenID Connect (OIDC) provider, so you integrate it with the OIDC Authorization Code Flow using a maintained client library — Authlib is the modern recommendation.
This guide shows a complete, production-shaped integration on Django 5.x + Authlib: create the Auth0 application, configure environment variables, register the OAuth client, and implement login, callback, logout, user mapping, token storage, and view protection — with the security details (state, nonce, HTTPS) that make it safe.
SSO and OIDC in one minute (vs SAML)
OIDC is an identity layer built on top of OAuth 2.0. After the user authenticates at Auth0, your app receives an ID token (a signed JWT describing the user) plus an access token. SAML is the older XML-based federation standard still common in large enterprises; Auth0 speaks both, but for a new Django integration OIDC is simpler, JSON-based, and the default. Use SAML only when a specific enterprise customer mandates it.
The Authorization Code Flow for a server-rendered Django app works like this:
- User clicks Log in; Django redirects the browser to Auth0's
/authorizeendpoint with astateandnonce. - The user authenticates at Auth0 (password, social, MFA — Auth0's problem, not yours).
- Auth0 redirects back to your callback URL with a short-lived authorization
code. - Django exchanges that code (server-to-server) at Auth0's
/oauth/tokenendpoint for an ID token and access token. - Django validates the tokens, reads the user profile, and creates or fetches the matching Django
User.
Because the code-for-token exchange happens server-side with your client secret, tokens never sit in the browser — this is why the Authorization Code Flow is preferred over the old client-side auth0.js/Lock checkSession approach.
Why Auth0 for a Django app
- You stop owning credentials. Password storage, breach-detection, MFA, and account recovery live in Auth0.
- Social and enterprise logins for free effort. Google, Microsoft, GitHub, plus SAML/OIDC enterprise connections are configuration, not code.
- Standards-based. Any maintained OIDC client works; you are not locked into a proprietary SDK.
- Real SSO across apps. Several apps pointing at the same Auth0 tenant share one session — log in once, reach all of them.
As of 2026 Auth0 has a free tier suitable for development and small apps, with paid tiers priced by monthly active users; pricing changes often, so verify the current tiers on auth0.com rather than trusting any blog (this one included).
Step 1 — Create the Auth0 application
In the Auth0 Dashboard, create an application of type Regular Web Application (server-rendered Django is confidential — it can keep a client secret). Then on the application's Settings tab note three values and configure two URL lists:
- Domain — e.g.
dev-abc123.us.auth0.com(your tenant's issuer). - Client ID and Client Secret.
- Allowed Callback URLs — where Auth0 returns the code, e.g.
http://localhost:8000/callback/for dev andhttps://app.example.com/callback/for production. - Allowed Logout URLs — where Auth0 may return after logout, e.g.
http://localhost:8000/andhttps://app.example.com/.
The callback and logout URLs must match exactly (scheme, host, port, trailing slash) or Auth0 rejects the request — this is the single most common setup error.
Step 2 — Install Authlib and store secrets in the environment
Never hardcode the client secret. Read it from environment variables (or your secrets manager).
# Install the modern OIDC client
pip install "Authlib>=1.3" requests
# .env (load with python-dotenv, django-environ, or your platform's secret store)
AUTH0_DOMAIN=dev-abc123.us.auth0.com
AUTH0_CLIENT_ID=your-client-id
AUTH0_CLIENT_SECRET=your-client-secret
DJANGO_SECRET_KEY=change-meAuthlib is the maintained, well-documented choice today. mozilla-django-oidc is a solid alternative if you prefer a batteries-included Django app with a pluggable auth backend. The much older python-social-auth and hand-rolled python-jose token-parsing approaches still appear in tutorials, but for a new build prefer a maintained OIDC client and let it validate the ID token signature, nonce, and state for you.
Step 3 — Settings and the OAuth registry
Load the Auth0 values in settings.py:
# settings.py
import os
AUTH0_DOMAIN = os.environ["AUTH0_DOMAIN"]
AUTH0_CLIENT_ID = os.environ["AUTH0_CLIENT_ID"]
AUTH0_CLIENT_SECRET = os.environ["AUTH0_CLIENT_SECRET"]
# Where @login_required sends anonymous users, and where to land after login.
LOGIN_URL = "login"
LOGIN_REDIRECT_URL = "dashboard"
# In production, sessions and the OAuth state must travel over HTTPS only.
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = TrueRegister the Auth0 client once, using its discovery URL so Authlib auto-loads the authorize, token, JWKS, and userinfo endpoints. Put this in its own module, e.g. accounts/oauth.py:
# accounts/oauth.py
from authlib.integrations.django_client import OAuth
from django.conf import settings
oauth = OAuth()
oauth.register(
"auth0",
client_id=settings.AUTH0_CLIENT_ID,
client_secret=settings.AUTH0_CLIENT_SECRET,
# Discovery endpoint: Authlib fetches all OIDC metadata + JWKS from here.
server_metadata_url=f"https://{settings.AUTH0_DOMAIN}/.well-known/openid-configuration",
client_kwargs={
"scope": "openid profile email",
# add "offline_access" to also receive a refresh token (see below)
},
)Step 4 — Login, callback, and logout views
The three views below implement the whole flow. authorize_redirect generates and stores the state and nonce in the session; authorize_access_token validates them, exchanges the code, verifies the ID token signature against Auth0's JWKS, and returns the parsed claims under token["userinfo"].
# accounts/views.py
from urllib.parse import quote_plus, urlencode
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth import login as django_login, logout as django_logout
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect, render
from django.urls import reverse
from .oauth import oauth
from .services import upsert_user
User = get_user_model()
def login(request):
"""Send the browser to Auth0's hosted login page."""
redirect_uri = request.build_absolute_uri(reverse("callback"))
return oauth.auth0.authorize_redirect(request, redirect_uri)
def callback(request):
"""Auth0 redirects here with ?code=...; exchange it and sign the user in."""
token = oauth.auth0.authorize_access_token(request) # validates state + nonce + signature
userinfo = token["userinfo"] # verified ID-token claims
user = upsert_user(userinfo)
django_login(request, user, backend="django.contrib.auth.backends.ModelBackend")
# Keep tokens only if you need to call APIs later; the session is server-side.
request.session["auth0_token"] = token
return redirect(settings.LOGIN_REDIRECT_URL)
def logout(request):
"""Clear the Django session, then log out of Auth0 (single sign-out)."""
django_logout(request)
return_to = request.build_absolute_uri(reverse("index"))
params = urlencode(
{"returnTo": return_to, "client_id": settings.AUTH0_CLIENT_ID},
quote_via=quote_plus,
)
return redirect(f"https://{settings.AUTH0_DOMAIN}/v2/logout?{params}")
@login_required
def dashboard(request):
"""A protected view — anonymous users are bounced to LOGIN_URL."""
return render(request, "dashboard.html", {"user": request.user})Step 5 — URLs
Map the views; the callback path must match the Allowed Callback URLs you set in Auth0.
# urls.py
from django.urls import path
from accounts import views
urlpatterns = [
path("", views.index, name="index"),
path("login/", views.login, name="login"),
path("callback/", views.callback, name="callback"),
path("logout/", views.logout, name="logout"),
path("dashboard/", views.dashboard, name="dashboard"),
]Mapping the Auth0 profile to a Django User
The OIDC userinfo claims give you a stable identifier (sub, e.g. auth0|abc123 or google-oauth2|...), plus email, name, and so on. Key your records on sub — it never changes — rather than email, which a user can edit. The default Django User model has no clean field for sub, so the simplest robust pattern is to store it as the username; here is a compact helper:
# accounts/services.py
from django.contrib.auth import get_user_model
User = get_user_model()
def upsert_user(userinfo):
"""Create or update a Django user from verified Auth0 OIDC claims."""
sub = userinfo["sub"] # immutable Auth0 user id
email = userinfo.get("email", "")
# Username must be unique; the Auth0 sub is globally unique and stable.
user, _created = User.objects.get_or_create(
username=sub,
defaults={"email": email},
)
# Keep mutable profile fields in sync on every login.
user.email = email
user.first_name = userinfo.get("given_name", "")
user.last_name = userinfo.get("family_name", "")
user.save(update_fields=["email", "first_name", "last_name"])
return userNote: get_or_create(username=sub) writes the sub directly via the ORM, which bypasses the username form-validator that would reject the | character. For a larger app, prefer a custom user model (AbstractUser) with an indexed auth0_sub field, or a related Profile model, so email and username stay human-friendly while sub remains the join key.
Refresh tokens and session security
Access tokens are short-lived. If your Django app must call an API on the user's behalf after the access token expires, request the offline_access scope (Step 3) so Auth0 also returns a refresh token, then let Authlib refresh transparently by passing an update_token callback to OAuth() that re-persists the rotated token. If you only need to know who is logged in, you do not need refresh tokens at all — the Django session cookie (server-side session) already carries the authenticated state.
Hardening checklist:
- HTTPS everywhere in production; set
SESSION_COOKIE_SECUREandCSRF_COOKIE_SECURE = True. - state (CSRF protection on the redirect) and nonce (replay protection on the ID token) are generated and verified automatically by Authlib — do not disable them.
- Validate the ID token signature;
authorize_access_tokendoes this against Auth0's JWKS via the discovery URL, so never decode tokens by hand. - Rotate refresh tokens and enable refresh-token rotation in the Auth0 dashboard.
- Layer in multi-factor authentication — enable it in Auth0, or for app-level TOTP see our guide on multi-factor authentication in Django.
Auth0 vs rolling your own vs other identity providers
| Option | Best when | Trade-off |
|---|---|---|
| Auth0 | You want hosted login, social + enterprise SSO, and MFA without owning credentials | Vendor cost scales with monthly active users; some lock-in around rules/actions |
| Roll your own (django.contrib.auth) | Simple single app, no SSO/MFA needs, full control required | You own password security, MFA, breach response, and social/enterprise federation |
| AWS Cognito | Already deep in AWS; want the IdP near your infra | Rougher developer experience; weaker enterprise-federation tooling |
| Okta | Enterprise/workforce identity is the priority | Heavier and pricier for small product teams (Okta now owns Auth0) |
| Keycloak (self-hosted) | You need open-source and full data control on your own servers | You operate, patch, scale, and secure it yourself |
All of these speak OIDC, so the Django/Authlib code above changes only in the server_metadata_url and scopes — switching IdPs is a config change, not a rewrite.
Frequently Asked Questions
Is Auth0 free for Django applications?
Auth0 offers a free tier that is fine for development and small production apps, with paid tiers priced by monthly active users and features. Exact limits and prices change regularly, so confirm the current plans on auth0.com (verify as of 2026). The Django integration itself uses the open-source Authlib library at no cost.
Should I use OIDC or SAML to connect Django to Auth0?
Use OpenID Connect (OIDC) for a new Django integration — it is JSON-based, simpler, and the default for modern apps, and it is what Authlib's Authorization Code Flow implements. Choose SAML only when a specific enterprise customer requires it; Auth0 supports both, so you can add a SAML connection later without rewriting your app.
Do I still need python-social-auth or python-jose for Auth0?
No. For a new build, use a maintained OIDC client such as Authlib (shown here) or mozilla-django-oidc. They handle the code exchange, ID-token signature validation, state, and nonce for you. The older python-social-auth and manual python-jose decoding approaches still work and appear in old tutorials, but they leave more security details for you to get right.
How do I map Auth0 users to Django's User model?
In your callback view, read the verified userinfo claims and key your records on the immutable sub claim rather than email. Use get_or_create to create or fetch the Django User, then call django.contrib.auth.login so @login_required and request.user work normally. For production, store sub on a custom user model or related profile and keep email and username human-friendly.
How do refresh tokens work with Auth0 and Authlib?
Request the offline_access scope so Auth0 returns a refresh token alongside the access token, then give OAuth() an update_token callback so Authlib refreshes expired access tokens automatically and re-persists the rotated token. If you only need to know who is signed in, the Django session already handles that and you do not need refresh tokens at all.
How do I implement single logout across multiple Django apps?
Clear the local Django session with logout(request), then redirect to Auth0's /v2/logout?returnTo=...&client_id=... endpoint so the central Auth0 session ends too. Because every app trusts the same Auth0 tenant, ending that session logs the user out everywhere. The returnTo URL must be listed in the application's Allowed Logout URLs.
At MicroPyramid we have spent 12+ years and 50+ delivered projects building secure authentication, SSO, and identity flows for Python and Django products — from Auth0 and Cognito integrations to custom OIDC providers. If you are adding SSO to a Django application and want it done with the security details handled correctly, our custom software development team can help.