This release is a pre-release and may not be stable for production use.
django-bastion
Enterprise SSO and identity governance for Django. Puts the admin behind your identity provider, turns the group claim into staff and superuser rights, records who got what and when, and leaves you a fire escape for the morning the provider is down.
Status: alpha, version 0.0.1a5. The API can change in any later version, patch releases included, and nothing is promised about upgrades until 1.0. OIDC works end to end. SAML, SCIM and the rule engine do not exist yet, and the pages that mention them say so.
pip install --pre django-bastion
--pre is not optional: 0.0.1a5 is a pre-release, and pip skips those unless asked.
Quickstart
Three edits. Assumes Microsoft Entra ID. Another provider is the same shape with a different issuer and
a different provider name; the how-to pages cover the ones with quirks worth knowing.
# settings.py
INSTALLED_APPS = [
"bastion",
"bastion.admin.apps.BastionAdminConfig", # replaces "django.contrib.admin"
...
]
AUTHENTICATION_BACKENDS = ["bastion.backends.SSOBackend"]
BASTION = {
"CONNECTIONS": {
"corp": {
"provider": "entra",
"issuer": "https://login.microsoftonline.com/<tenant-id>/v2.0",
"client_id": env("BASTION_CLIENT_ID"),
"client_secret": env("BASTION_CLIENT_SECRET"),
"staff_groups": ["django-staff"],
"superuser_groups": ["django-admins"],
},
},
# require_mfa refuses admin access when the assertion showed one factor.
# Off by default: confirm your provider emits `amr` with a real sign-in
# first, because several make it opt-in.
"ADMIN": {"connection": "corp", "require_mfa": True},
}
# urls.py
urlpatterns = [
path("sso/", include("bastion.urls")),
path("admin/", admin.site.urls),
]
$ python manage.py migrate
$ python manage.py bastion_doctor
bastion_doctor walks the path before anyone tries to use it. It reaches the provider for the discovery
document, the JWKS and the signing algorithms, compares your clock against theirs, and works through the
local half from the session engine to whether break-glass has anyone to alert.
Three things it will not tell you are fine. Whether the redirect URI is actually registered at the provider, whether the group claim is emitted and in what shape, and whether MFA will really be asserted: none of those can be known without a person completing a real login, so they come back marked unverifiable with the reason attached. A run that quietly skipped them would read better and help less. Most SSO debugging is a config typo three layers down, and a green tick over an unasked question is how you lose an afternoon to it.
That is the entire happy path. Everything below is optional.
What you get that you don't have today
The admin actually goes through SSO. Django's admin owns its own login view and ignores LOGIN_URL
entirely — grep -rn LOGIN_URL django/contrib/admin/ returns nothing, in every version from 5.2 to main.
Working around that is a known source of redirect loops and of the "authenticated but not staff" dead end.
We subclass AdminSite (the documented seam) and fail with a real 403 page that tells the person which
group they're missing and who to ask.
Claims map to roles, and in 0.1 that mapping is deliberately small. Two lists per connection,
staff_groups and superuser_groups, matched against the group claim. That is all of it. The ordered rule
engine with a serializable condition tree is the 0.2 design and is not built yet;
the roadmap says what it will look like and which two approaches were
already rejected.
Break-glass you can actually rely on. Creating one requires a written reason. Using one alerts a channel that does not depend on the identity provider, which matters because the outage that sends you here may be the provider itself. Both outcomes are recorded at critical severity, so a failed attempt is as visible as a successful one, and the account set can be restricted by network.
The model also refuses to delete the last active account. Deleting your way to zero is the sort of thing people do while tidying up, and the consequence only appears during the incident that needed it. There is a drill command for the same reason: an emergency account nobody has ever signed in with is an emergency account nobody knows works.
An audit log built for the people who will ask for it. Append-only, hash-chained, with the field set that NIST AU-3, PCI 10.2.2 and ISO 27002 8.15 independently converge on, plus a gapless sequence number so an exported sample can be shown to be complete.
Why you might not want this
Worth reading before you adopt it.
- If you only need social login, use django-allauth. It is mature, it has every provider, and this package is not trying to replace it.
- If you're a B2B SaaS and can expense it, WorkOS or Stytch will get you enterprise SSO faster than building will. Check their current per-connection pricing rather than trusting a figure in someone's README. We're the better answer when self-hosting is a requirement rather than a preference, when the Django admin itself is the thing you need behind SSO, or when connection count makes per-connection pricing hurt.
- If you need only OIDC and nothing else, mozilla-django-oidc is about 1,200 lines and you can read all of it in twenty minutes. That legibility is a real feature. Come here when you need the governance layer on top.
- We are pre-1.0 and the bus factor is currently 1. That should disqualify us from anything you cannot afford to fork. See GOVERNANCE.md, which states this plainly rather than burying it.
Requirements
Python 3.11+, Django 5.2 LTS or newer. PostgreSQL is what we recommend in production. SQLite, MySQL and MariaDB all pass the full test suite on every push. Oracle is not supported. SUPPORT_MATRIX.md has the versions each backend was actually run against, and the version-dropping policy.
There is a [saml] extra, and installing it today gets you pysaml2 and nothing else, because the SAML
implementation does not exist yet. The extra is reserved so the install line will not change on the day it
does, and it stays optional because pysaml2 pulls in xmlsec, which needs system packages.
Documentation
Not published to a site yet, but written and in the repository. Start at docs/index.md.
If you are evaluating rather than building, read these four in order: threat model, why you might not want this, data inventory, and the deployment checklist.
If you are integrating, your first login then the Entra guide.
Security
Report vulnerabilities privately through GitHub, not the issue tracker. SECURITY.md has the process, the response times we actually commit to, and an honest list of what we do not do.
Licence
Apache-2.0. Chosen for the patent grant rather than to match Django, because federated identity has enough patent history that the defensive-termination clause is worth having.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_bastion-0.0.1a5.tar.gz.
File metadata
- Download URL: django_bastion-0.0.1a5.tar.gz
- Upload date:
- Size: 218.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8ba5c27ae3ebe154b1e4c96823e059c5e644fa53adb63411ed4fbc03e1e38484
|
|
| MD5 |
684166f21ffc46aebae15a2e3fd24321
|
|
| BLAKE2b-256 |
637b3fe0458c9e74f6a850fb7583231dca85a2889aee3c2b349f31eec6d8aad1
|
Provenance
The following attestation bundles were made for django_bastion-0.0.1a5.tar.gz:
Publisher:
release.yml on thesaadmirza/django-bastion
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_bastion-0.0.1a5.tar.gz -
Subject digest:
8ba5c27ae3ebe154b1e4c96823e059c5e644fa53adb63411ed4fbc03e1e38484 - Sigstore transparency entry: 2313506556
- Sigstore integration time:
-
Permalink:
thesaadmirza/django-bastion@eef8bc8ffdd0b7bad475b07bd37fa0e25847620d -
Branch / Tag:
refs/tags/v0.0.1a5 - Owner: https://github.com/thesaadmirza
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eef8bc8ffdd0b7bad475b07bd37fa0e25847620d -
Trigger Event:
push
-
Statement type:
File details
Details for the file django_bastion-0.0.1a5-py3-none-any.whl.
File metadata
- Download URL: django_bastion-0.0.1a5-py3-none-any.whl
- Upload date:
- Size: 129.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5624569bd343cdda3ba01342d16a40e492b17b997ab8a701c7ad361dee3dd877
|
|
| MD5 |
98bd86b0e5bfb18dc37239abab138f7f
|
|
| BLAKE2b-256 |
60f2ef428ac2a4598e8343d377218b9e20271a0b4966e4750c95b2183aa4fd93
|
Provenance
The following attestation bundles were made for django_bastion-0.0.1a5-py3-none-any.whl:
Publisher:
release.yml on thesaadmirza/django-bastion
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_bastion-0.0.1a5-py3-none-any.whl -
Subject digest:
5624569bd343cdda3ba01342d16a40e492b17b997ab8a701c7ad361dee3dd877 - Sigstore transparency entry: 2313506563
- Sigstore integration time:
-
Permalink:
thesaadmirza/django-bastion@eef8bc8ffdd0b7bad475b07bd37fa0e25847620d -
Branch / Tag:
refs/tags/v0.0.1a5 - Owner: https://github.com/thesaadmirza
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eef8bc8ffdd0b7bad475b07bd37fa0e25847620d -
Trigger Event:
push
-
Statement type: