Stolen session cookies stop working. django-dbsc adds Device Bound Session Credentials
(DBSC) to Django: after login, the browser binds the session to a private key
that never leaves the device (TPM or Secure Enclave). An attacker who copies the sessionid cookie with malware or an
infostealer gets a 401, because they can't prove they hold that key.
Without DBSC: stolen sessionid ──▶ attacker is logged in as the victim
With DBSC: stolen sessionid ──▶ 401 (no device key, no valid bound cookie)
- Drop-in: one app, one middleware, two URLs. Your login and logout views stay as they are.
- Graceful: browsers without DBSC support keep working exactly as before.
- Your choice of strictness: protect everyone opportunistically, guard sensitive views, or require binding for staff.
- WSGI and ASGI: the middleware and decorator run natively async under ASGI.
Status: alpha (0.1.0). It has been verified end to end in Chrome 156 (see docs/real-browser-testing.md). The API may still change before 1.0. See the CHANGELOG for known limitations.
- Python ≥ 3.12, Django ≥ 5.2
- HTTPS, including in local development. Chrome ignores DBSC on plain
http://, even onlocalhost. - A cache shared by all processes (Redis, Memcached or the database cache). The default per-process
LocMemCachebreaks with more than one worker.
uv add "django-dbsc>=0.1.0" # or: pip install "django-dbsc>=0.1.0"(0.0.1 on PyPI is an empty name placeholder. Make sure you get 0.1.0 or later.)
# settings.py
INSTALLED_APPS = [
# ...
"django_dbsc",
]
MIDDLEWARE = [
"django.contrib.sessions.middleware.SessionMiddleware",
# ...
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django_dbsc.middleware.DeviceBoundSessionMiddleware", # after Session and Authentication middleware
# ...
]
DJANGO_DBSC = {
# Paths the browser must reach without a bound cookie. Always keep the DBSC endpoints here.
"EXEMPT_PATH_PREFIXES": ["/dbsc/", "/static/"],
}# urls.py
from django.urls import include, path
urlpatterns = [
# ...
path("dbsc/", include("django_dbsc.urls")),
]python manage.py migrateThat's it. Every login now offers the browser a device binding, and bound sessions are protected automatically.
To require a proven device for a sensitive view:
from django.contrib.auth.decorators import login_required
from django_dbsc.decorators import device_bound_required
@login_required
@device_bound_required
def change_password(request): ...| Read this | If you want to |
|---|---|
| User guide | understand the three protection modes, use request.dbsc_bound and the decorator, revoke devices, deploy |
| Configuration | look up a setting, its default and when to change it |
| How it works & security model | know exactly what is and isn't protected |
| Troubleshooting | find out why a browser doesn't bind, or why users get logged out |
DBSC is a W3C draft. At the time of writing:
| Browser | Status |
|---|---|
| Chrome on Windows | Enabled by default since Chrome 145 (needs a TPM) |
| Chrome on macOS | Since Chrome 147, rolling out gradually (Secure Enclave) |
| Firefox, Safari | Not supported. Sessions work as before, without binding. |
You don't need to detect support. Browsers without DBSC ignore the registration offer.