Skip to main content
Pre-release

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

django_bastion-0.0.1a5.tar.gz (218.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_bastion-0.0.1a5-py3-none-any.whl (129.4 kB view details)

Uploaded Python 3

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

Hashes for django_bastion-0.0.1a5.tar.gz
Algorithm Hash digest
SHA256 8ba5c27ae3ebe154b1e4c96823e059c5e644fa53adb63411ed4fbc03e1e38484
MD5 684166f21ffc46aebae15a2e3fd24321
BLAKE2b-256 637b3fe0458c9e74f6a850fb7583231dca85a2889aee3c2b349f31eec6d8aad1

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_bastion-0.0.1a5.tar.gz:

Publisher: release.yml on thesaadmirza/django-bastion

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_bastion-0.0.1a5-py3-none-any.whl.

File metadata

File hashes

Hashes for django_bastion-0.0.1a5-py3-none-any.whl
Algorithm Hash digest
SHA256 5624569bd343cdda3ba01342d16a40e492b17b997ab8a701c7ad361dee3dd877
MD5 98bd86b0e5bfb18dc37239abab138f7f
BLAKE2b-256 60f2ef428ac2a4598e8343d377218b9e20271a0b4966e4750c95b2183aa4fd93

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_bastion-0.0.1a5-py3-none-any.whl:

Publisher: release.yml on thesaadmirza/django-bastion

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page