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.1a6. 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.1a6 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.

Metadata

Release files for django-bastion 0.0.1a6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-bastion 0.0.1a6
File Size Uploaded
django_bastion-0.0.1a6.tar.gz 223.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-bastion 0.0.1a6
File Interpreter ABI Platform
django_bastion-0.0.1a6-py3-none-any.whl Python 3 none any Details

Total release size: 354.4 kB

Release files / django_bastion-0.0.1a6.tar.gz

Download URL django_bastion-0.0.1a6.tar.gz
Size 223.2 kB
Tags Source
SHA-256 checksum
How to use checksums
d422e09e37c2e72694e25f04ffc2a590b163cbf24b01ad8b221d3b8567034159
BLAKE2b-256 checksum
How to use checksums
30cd8473622009b13041dd9db16748a801377fa36f3b1fa9b1687ed59c230ae7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log

Release files / django_bastion-0.0.1a6-py3-none-any.whl

Download URL django_bastion-0.0.1a6-py3-none-any.whl
Size 131.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2d80afe2a86864134b8eac487df3df337ddb93b3d4994ca59c2e14d5de970218
BLAKE2b-256 checksum
How to use checksums
fbbbeea7cdaf00c82f9df66b40906f8213e4c7ab9b56087d6dc6ae068fd8a369
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page