mozilla-django-oidc
OpenID Connect authentication for Django, with a pluggable auth backend, callback views, and silent session refresh.
Repository Health
Technical Analysis
mozilla-django-oidc is a lightweight authentication and access-management library that plugs OpenID Connect (OIDC) login into Django applications. Built and maintained by Mozilla, it implements the OIDC authorization code flow as a Django authentication backend, so it drops into AUTHENTICATION_BACKENDS alongside Django’s existing auth system rather than replacing it.
The library ships ready-made views for initiating login, handling the OP callback, and logging out, plus a SessionRefresh middleware that silently re-validates a user’s ID token in the background and forces re-authentication once it expires. Token verification supports both HMAC and RSA/EC signing algorithms, with JWKS-based key retrieval and PKCE support for public clients. Nearly every step — username generation, claim verification, user creation, user matching — is exposed as an overridable method, so teams can adapt it to Mozilla-specific or custom identity requirements without forking the library.
What You Get
- OIDCAuthenticationBackend - a
ModelBackendsubclass implementing the OIDC authorization code flow: token exchange, JWT/JWS signature verification (HS/RS/ES algorithms), claim verification, and user lookup-or-creation by email. - Ready-made views -
OIDCAuthenticationRequestView,OIDCAuthenticationCallbackView, andOIDCLogoutViewhandle the full login/callback/logout HTTP flow, including CSRF-safe state/nonce/PKCE handling stored in the session. - SessionRefresh middleware - transparently re-validates the user’s ID token expiry on each request and silently redirects through the OP with
prompt=nonewhen it has expired, including a JSON response path for XHR requests. - PKCE support - optional Proof Key for Code Exchange (RFC 7636) with configurable code-verifier length and S256/plain challenge methods, for public or SPA-fronted clients.
- JWKS-based key retrieval - fetches and matches signing keys from the OP’s JWKS endpoint by
kid/alg, used automatically for RS/ES-signed tokens when no static key is configured. - DRF integration - an optional
mozilla_django_oidc.contrib.drfmodule providing a Django REST Framework authentication class that layers on top of the same backend.
Common Use Cases
- Enterprise SSO for internal Django tools - authenticate employees against a corporate OIDC provider (Okta, Azure AD, Keycloak) without building a custom OAuth flow.
- Federated login for public Django apps - let users sign in with an external identity provider while keeping Django’s user model and permissions system as the source of truth.
- API authentication for DRF services - use the bundled DRF authentication class so REST endpoints accept the same OIDC-issued tokens as the web frontend.
- Long-lived session hardening - deploy the SessionRefresh middleware so authenticated sessions are periodically re-checked against the identity provider instead of trusting a stale Django session indefinitely.
- Custom identity workflows - override
create_user,filter_users_by_claims,verify_claims, or the username algorithm to match claims to existing accounts using organization-specific rules.
Under The Hood
Architecture
The library is organized around Django’s own auth extension points rather than a custom framework: auth.py defines OIDCAuthenticationBackend(ModelBackend), which Django’s authenticate() dispatches to once registered in AUTHENTICATION_BACKENDS; views.py provides the three class-based views that drive the authorization-code handshake (request, callback, logout) and store per-request state (state, nonce, PKCE verifier) in the Django session under an oidc_states dict keyed by the OIDC state parameter; middleware.py adds SessionRefresh, a MiddlewareMixin that inspects request.session['oidc_id_token_expiration'] on every eligible GET request and silently redirects through the OP with prompt=none once expired. Nearly every method on the backend and views (create_user, filter_users_by_claims, verify_claims, get_userinfo, get_username) is designed to be overridden in a subclass, so the library reads as a small set of well-factored extension points rather than a monolith. A change to the core authenticate() flow would ripple into every overriding subclass downstream, so the maintainers keep that method’s contract stable across releases.
Tech Stack
Pure Python targeting Django >= 4.2 (tested through 5.2) on Python 3.10+. Direct dependencies are minimal and deliberate: PyJWT for JWT decoding/verification (including JWKS key handling via PyJWK), requests for token-endpoint and userinfo HTTP calls, and cryptography as PyJWT’s backend for RS/ES algorithms. Packaging uses setuptools with pyproject.toml-driven metadata and a dynamic version sourced from mozilla_django_oidc/__init__.py. An optional contrib/drf.py module adds a Django REST Framework authentication class, gated behind DRF being installed. CI runs unit tests via GitHub Actions across the supported Python/Django matrix plus a separate Docker Compose-based integration-test suite against real test OIDC provider and relying-party containers, with coverage tracked through Codecov.
Code Quality
The test suite is substantial and specific: test_auth.py (1400+ lines), test_middleware.py, test_views.py, and test_contrib_drf.py cover the token-exchange flow, signature verification across algorithms, JWKS key matching, PKCE, nonce/state handling, and middleware refresh logic, run with Django’s test framework against a dedicated tests/settings.py. Error handling is explicit and typed: the library raises Django’s SuspiciousOperation for security-relevant failures (state mismatch, nonce mismatch, JWS verification failure) rather than swallowing them, and wraps token-endpoint HTTP errors in a custom HTTPError subclass with the response body attached for debugging. Linting is enforced via flake8 (run standalone or through tox -e lint) with pre-commit hooks available for contributors. There is no static type-checking (no mypy config, no inline type hints beyond docstrings), which is the main gap relative to a fully typed modern library.
API Design
The public surface is deliberately small: install the backend, mount the provided URLs, add ~10 required settings, and login/callback/logout work out of the box. Every extension point is a well-named, single-purpose method (create_user, update_user, get_username, verify_claims) rather than a monolithic hook, which keeps subclassing predictable. Configuration is entirely through Django settings (OIDC_RP_CLIENT_ID, OIDC_OP_TOKEN_ENDPOINT, etc.) read via a small import_from_settings helper that raises ImproperlyConfigured with a clear message on missing required values, matching Django’s own configuration-error conventions. Documentation is served from Read the Docs with a dedicated settings reference and DRF/XHR integration pages, and the README’s design-principles section doubles as an implicit style guide for anyone extending the backend.