django-device-cookies 0.2.0
Login throttling with device cookies
pip install django-device-cookies==0.2.0
Django Device Cookies
Throttle login attempts with device cookies. Each login sets a signed cookie, and failed attempts count per cookie, or per username for requests without a valid one.
Why
Temporary account lockout after several failed attempts is an easy target for denial of service: a fixed lockout policy lets an attacker lock selected users out of the site. Locking the account/IP pair instead is better against denial of service but weaker against botnets and proxies, and harder to implement correctly. Device cookies are a variant of account/IP blocking that uses a browser cookie instead of an IP address. The throttle trusts a client that logged in before and locks it out individually. All untrusted clients share one temporary lockout per account. That bounds the guesses against an account to DEVICE_COOKIE_ATTEMPTS_PER_PERIOD per DEVICE_COOKIE_PERIOD regardless of the size of the botnet.
Installation
Add "django_device_cookies" to INSTALLED_APPS.
Add "django_device_cookies.middleware.DeviceCookieMiddleware" to MIDDLEWARE.
Put "django_device_cookies.backends.DeviceCookieBackend" first in AUTHENTICATION_BACKENDS, before ModelBackend or your own backends.
Run manage.py migrate.
With two backends, a call to login() for a user that did not come from authenticate() must pass backend, as the Django documentation says.
django_device_cookies.backends.DeviceCookieModelBackend is ModelBackend with the throttle built in, for projects that want a single entry. Django stores the path of the backend that authenticated a user in the session. Replacing ModelBackend with it logs out every existing session.
Settings
The defaults:
DEVICE_COOKIE_NAME = "django_device"
DEVICE_COOKIE_PERIOD = timedelta(minutes=15)
DEVICE_COOKIE_ATTEMPTS_PER_PERIOD = 5
DEVICE_COOKIE_MAX_AGE = timedelta(days=365)
DEVICE_COOKIE_SECURE = True
DEVICE_COOKIE_SAMESITE = "Lax"
DEVICE_COOKIE_DOMAIN = None
DEVICE_COOKIE_PATH = "/"
DEVICE_COOKIE_PER_USER = False
DEVICE_COOKIE_REVOKE_AFTER_FAILURES = None
DEVICE_COOKIE_PER_USER sets one cookie per user instead of per browser. DEVICE_COOKIE_REVOKE_AFTER_FAILURES stops trusting a cookie after that many failures over its life.
Password reset
An attacker can keep the untrusted lockout for an account in place, which blocks its user from logging in from a new device. OWASP suggests issuing a device cookie when the user visits a password reset link, since that proves possession of the email account. An actual password reset is not necessary. django_device_cookies.views.PasswordResetConfirmView does this. Add it to your URLs before include("django.contrib.auth.urls"):
from django_device_cookies.views import PasswordResetConfirmView
path(
"accounts/reset/<uidb64>/<token>/",
PasswordResetConfirmView.as_view(),
name="password_reset_confirm",
),
It renders registration/password_reset_confirm.html, like Django’s view.
Signals
- django_device_cookies.signals.lockout
Sent when an account or a device reaches the failed attempt limit.
Arguments sent with this signal:
- sender
The FailedAuthenticationAttempt class.
- username
The normalized username.
- device
The device cookie nonce, or "" for untrusted clients.
- request
The current HttpRequest, or None.
Maintenance
Nothing removes expired failed attempts automatically. The clear_device_cookie_attempts management command deletes them. Run it on a regular basis, for example as a daily cron job.
Limitations
The throttle covers only calls to authenticate() and aauthenticate(). Calls without a request count as untrusted.
Attempts sent in parallel can exceed the limit.
If Django masks your USERNAME_FIELD in the user_login_failed signal, the throttle ignores calls without a request that pass it by name.
System checks
device_cookies.E001: AUTHENTICATION_BACKENDS has no device cookie backend. Nothing throttles logins.
device_cookies.W001: The device cookie backend is not first in AUTHENTICATION_BACKENDS. A locked-out client that knows the password still logs in.
device_cookies.E002: MIDDLEWARE has no device cookie middleware. No client gets a device cookie, and every login attempt counts as untrusted.
device_cookies.E003: <setting> must be <expected>.
device_cookies.E004: Browsers reject a SameSite=None cookie that is not Secure. No client gets a device cookie.
device_cookies.E005: AUTHENTICATION_BACKENDS has only the device cookie backend. Nothing authenticates.
device_cookies.W003: Django masks USERNAME_FIELD <field> in the user_login_failed signal. The throttle ignores calls to authenticate() without a request that pass it by name.
This check runs only with the --deploy option:
device_cookies.W002: DEVICE_COOKIE_SECURE is off. Browsers send device cookies over HTTP.
Project links
Data verified by PyPI on Sep 25, 2026
Key dates
PyPI data
- Released:
1 maintainer
PyPI data
Credits
Data verified by PyPI on Sep 25, 2026
Author: Tom Carrick
GitHub Statistics
Data verified by PyPI on Sep 25, 2026
Requires
Python >=3.10
Classifiers
- Development Status
- Environment
- Framework
- Intended Audience
- Operating System
- Programming Language
- Topic
- Typing
Release files for django-device-cookies 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_device_cookies-0.2.0.tar.gz | 37.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_device_cookies-0.2.0-py3-none-any.whl 15.9 kB | Python 3 | none | any | Details |
Total release size: 53.5 kB
Release files / django_device_cookies-0.2.0.tar.gz
| Download URL | django_device_cookies-0.2.0.tar.gz |
|---|---|
| Size | 37.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
00588a42e033eece4e277d6d7a58dd7d8cc0827a3b7529edb2f7d470609a11a4
|
|
BLAKE2b-256 checksum How to use checksums |
c2ca577dbbdc2db07971c55acc59b8ba5389c35a5bcd768fb0e93b270f12ee30
|
| 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 Sep 25, 2026.
Transparency logIdentity
- Publishing platform
- GitHub Actions
- Publishing repository
- github.com/knyghty/django-device-cookies
- Publishing commit
- github.com/knyghty/django-device-cookies/tree/ba3b7f17b32ecda7467beddd710651c094abc7d1
Workflow
Artifact
- Subject
-
django_device_cookies-0.2.0.tar.gz - SHA-256 checksum
-
00588a42e033eece4e277d6d7a58dd7d8cc0827a3b7529edb2f7d470609a11a4Verifying attestations
Release files / django_device_cookies-0.2.0-py3-none-any.whl
| Download URL | django_device_cookies-0.2.0-py3-none-any.whl |
|---|---|
| Size | 15.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
767b152e5244fbf5c998ae0ee50f7d4a79fdcb7016bb143fa10706ac5d2dafa9
|
|
BLAKE2b-256 checksum How to use checksums |
de8af3acf41c34f775827b9f2173460a2ee8eb9a7df93ea79b07490c9a78a9f3
|
| 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 Sep 25, 2026.
Transparency logIdentity
- Publishing platform
- GitHub Actions
- Publishing repository
- github.com/knyghty/django-device-cookies
- Publishing commit
- github.com/knyghty/django-device-cookies/tree/ba3b7f17b32ecda7467beddd710651c094abc7d1
Workflow
Artifact
- Subject
-
django_device_cookies-0.2.0-py3-none-any.whl - SHA-256 checksum
-
767b152e5244fbf5c998ae0ee50f7d4a79fdcb7016bb143fa10706ac5d2dafa9Verifying attestations