# django-rest-knox: one token per device, hashed at rest, with an expiry you set

> Knox is a token authentication module for Django REST Framework that issues a separate token per login, stores only a secure hash of each token, and lets you configure token expiry in Django settings. It is a Jazzband project, MIT licensed, and supports Django 4.2 through 6.0 on Python 3.10 and newer.

**jazzband/django-rest-knox** — Authentication Module for django rest auth

- Repository: https://github.com/jazzband/django-rest-knox
- Stars: 1,266 · Forks: 225
- Language: Python
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/jazzband-django-rest-knox

## One token per login, not one token per user

Knox provides easy-to-use authentication for Django REST Framework, with the aim of allowing common patterns in REST-based applications with little extra effort, and of ensuring that connections remain secure.

The first difference from the built-in DRF `TokenAuthentication` is about multiplicity, and it is the reason the library exists at all. DRF tokens are limited to one per user, and the project describes the two consequences plainly. That does not facilitate securely signing in from multiple devices, because the token is shared between them. And it requires all devices to be logged out if a server-side logout is required, since the token is deleted, and there is only one.

Knox's answer is straightforward. It provides one token per call to the login view, so each client has its own token, and that token is deleted on the server side when the client logs out. Logging out on one phone no longer signs you out on your laptop.

The second capability is the counterpart, and it is the one that the shared-token design cannot offer. Knox also provides an option for a logged-in client to remove all tokens that the server has, forcing all clients to re-authenticate. That is the panic button, and it is only coherent because each device holds a distinct token rather than sharing one.

Taken together, these two operations cover the two things you actually need from a token system. Per-device revocation for normal operation, and global revocation for when you believe credentials have leaked. The DRF default gives you neither.

The trade is that a device count now means a token count, and every device is a thing that can be stolen, so the security of the system depends on how each device stores its token, which is a client-side concern the server can only influence by making tokens short-lived.

## Tokens are stored as hashes, so a database leak is not an account leak

The second problem Knox sets out to fix is about what happens when the database is compromised.

DRF tokens are stored unencrypted in the database, and the project is blunt about the implication. That would allow an attacker unrestricted access to an account with a token if the database were compromised.

Knox tokens are only stored in a secure hash form, like a password. Even if the database were somehow stolen, an attacker would not be able to log in with the stolen credentials.

The reasoning is worth stating precisely, because it is the difference between a credential and a reference. A DRF token row contains the actual token string, so reading the row is reading the credential. A Knox row contains a hash, so reading the row gives you a value that has to be matched against the hash of something the attacker does not have, which is the password-equivalent. The token itself only ever exists on the client.

This is the same design decision Django makes with user passwords, which is why the project uses the comparison directly, like a password. It also means a leaked token database can be attacked offline against a guess, which is a much weaker position for the attacker than a plaintext token, but it is not free of risk.

The third difference completes the set. DRF tokens track their creation time but have no inbuilt mechanism for tokens expiring. Knox tokens can have an expiry configured in the app settings, and the default is 10 hours.

An expiry turns a stolen token from a permanent key into a temporary one, and ten hours is a meaningful bound. It also means the client has to handle expiry, which is a real requirement rather than a detail, and the release history shows this being tightened. Version 5.0.2 implemented AUTO_REFRESH_MAX_TTL to limit total token lifetime when AUTO_REFRESH is True, which addresses the case where a client keeps refreshing its token and thereby keeps it alive indefinitely.

## The setting that decides your token lifetime

The configuration lives in Django settings, and one of the values matters more than the others.

The token expiry is configured in the app settings with a default of ten hours. That single number is the effective policy of your authentication system, and it has to be chosen against the shape of your clients. A mobile app that is used in short sessions can survive a short expiry, because the user re-authenticates with a password and gets a new token. A background job or a server-to-server client that expects to run for a week cannot.

The AUTO_REFRESH mechanism is the part that needs a decision rather than a default. A client with a valid token can obtain a new one without re-entering credentials, which is what keeps a long-lived session alive. The problem that creates is that a refreshable token that is refreshed indefinitely is a token that never expires. The AUTO_REFRESH_MAX_TTL option, implemented in version 5.0.2, limits the total token lifetime when AUTO_REFRESH is enabled, which caps that behaviour.

That release also falls between two others, and the spacing tells you about the maintenance pattern. Version 5.0.2 is from 2024-09-30, version 5.0.4 from 2026-03-10 with the description Minor release: Django version upgrades and fix n+1 query, and version 5.1.0 from 2026-07-12 with the description Packaging improvements and cleanups.

The n+1 query fix is worth a mention because it is the kind of change that matters in production. A token authentication check runs on every authenticated request, so a query per request in the token lookup path is a database round trip per API call, and a fix for that is a performance change for every consumer of the library rather than a feature.

The default branch is `develop`, and the last push to the repository was on 2026-10-05, so the work continues after the last release.

## A Jazzband project with a wide Django support range

django-rest-knox is a Jazzband project, and the packaging metadata reflects that organisation's conventions.

The version is declared dynamic, taken from `knox.__init__.__version__` through the setuptools dynamic version attribute, so the number lives in the source rather than in the project file. The setuptools packages find directive includes `knox*` and excludes `contrib*`, `docs*`, `tests*`, and `knox_project*`, which is how the documentation and the test project stay out of the distributed wheel while the `knox` package itself ships.

The runtime dependencies are two, `django>=4.2` and `djangorestframework>=3.14`, and the Python floor is 3.10. The framework classifiers cover Django 4.2, 5.0, 5.2, and 6.0, and the Python classifiers cover 3.10 through 3.14, with a Development Status of 5, Production/Stable.

That combination of a wide framework range and an old Python floor is the shape of a library that has to keep working across a version matrix, and the release history shows it doing exactly that. The 5.0.4 release is described as Django version upgrades, which is the maintenance work of a library whose users are themselves spread across Django versions.

The repository layout shows the standard Django application structure. There is the `knox/` package itself, a `knox_project/` directory which is the Django project used for the test suite, `manage.py` at the root, `tests/`, and `docs/`. The `docs/` directory is built with Mkdocs, configured by `mkdocs.yml`, and there is a `CONTRIBUTORS` file and a `CHANGELOG.md`.

There are several files that are worth pausing on. `knox_project/` being a real Django project rather than a settings module means the test suite runs against a realistic setup. `MANIFEST.in` is present for source distribution contents. `.flake8`, `.isort.cfg`, and `.pre-commit-config.yaml` are the lint and import-order configuration, and `.codecov.yml` is the coverage reporting configuration.

## The test matrix runs in Docker, and the docs run in Docker too

The instructions for running the tests locally are unusually practical, and they explain the project's own development constraints.

If you need to debug a test locally and have Docker installed, you can run the `./docker-run-tests.sh` script, and it will run the test suite in every Python and Django version. The alternative is running regular tox in the root folder, but the project notes that this makes testing the matrix of Python and Django versions a bit more tricky. The script exists because the matrix is wide enough, spanning four Django versions and five Python versions, that running it by hand is impractical.

The documentation has the same Docker treatment, which is a pattern worth noting. The documentation is generated by Mkdocs, and there is a `mkdocs.sh` script in the repository which runs mkdocs in a Docker container. Running the script without any parameters triggers the `serve` command, and the server is exposed on localhost on port 8000. To configure the port the `serve` command exposes the server to, you set the `MKDOCS_DEV_PORT` environment variable:

```
MKDOCS_DEV_PORT="8080"
```

You can also pass any mkdocs command, such as `./mkdocs build` or `./mkdocs --help`.

Putting the docs in a container too is a small decision with a real effect. It means a contributor can build and preview the documentation without installing Mkdocs and its theme into their own environment, and it means the documentation build cannot drift from what the maintainers see, because they all run the same image.

The documentation itself is published at jazzband.github.io/django-rest-knox, and the project metadata points the Documentation link at the Jazzband project page instead, which is the listing for the project within the organisation.

## Where Knox is the wrong choice

The boundaries are specific, and most of them follow from the fact that this is a token scheme for API clients rather than a session scheme for browsers.

First, if your application is a server-rendered Django site with a single user interface, this is overkill. Django's own session framework already gives you a per-client credential, a server-side invalidation path, and an expiry. A token in a localStorage-bearing SPA or a mobile app is a different requirement, and that is where Knox belongs.

Second, and this is the operational reality, a token in a mobile app is a token in a device an attacker can read. Knox solves the storage-on-the-server problem by hashing, and it solves the lifetime problem with an expiry, but the token still has to live somewhere on the client, and on a rooted or jailbroken device that somewhere is reachable. A shorter expiry is the mitigation, and the ten-hour default is a policy choice you may want to shorten. The library does not and cannot remove the client-side risk, and it does not claim to.

Third, the token records live in your database. That is a deployment requirement, not a bug, and it belongs in your infrastructure plan.

Fourth, token expiry is a client-side feature you will have to build. When a token expires mid-request, the client receives an authentication error and has to re-authenticate, refresh, or prompt. In a mobile app that means a login screen appearing at an awkward moment, and the handling of that is application code.

Fifth, the library is one module in the Django REST Framework stack, so it inherits DRF's own version cadence. The dependency floor of `djangorestframework>=3.14` and the Django range from 4.2 to 6.0 mean a DRF major upgrade is the event that could break your authentication, and the last release, 5.1.0, was about packaging rather than a compatibility change.

Sixth, and this is a governance point, the project lives inside Jazzband, a collective of maintainers, and that structure is what turns a single-maintainer library into one with a succession path. It also means the roadmap is negotiated rather than set by one person, which is slower and steadier.

## Conclusion

Knox fits a Django REST Framework application with clients on several devices, where a single shared token is a liability, and where you want tokens to expire on their own and to be revocable in bulk. It is the wrong choice for a browser-only application, where Django's own session cookies already do this job without a token, and for an API whose clients cannot store a secret securely. Check three things before you adopt it: that the token lifetime setting suits your product, since the default is ten hours, that your client can handle a token expiring and re-authenticating, and that the database is a place you are willing to store hashed token records.

## FAQ

### How do I install django-rest-knox and what does it add over DRF tokens?

The package is `django-rest-knox`, MIT licensed, requiring Python 3.10 or newer with `django>=4.2` and `djangorestframework>=3.14`. It differs from DRF's built-in token authentication in three ways: one token per login call rather than one per user, tokens stored only as a secure hash rather than unencrypted, and a configurable expiry with a default of 10 hours.

### How do I set the token lifetime in django-rest-knox?

Token expiry is configured in the Django app settings, with a default of 10 hours. Related to it, version 5.0.2 implemented AUTO_REFRESH_MAX_TTL to limit total token lifetime when AUTO_REFRESH is True, so a client that refreshes its token cannot keep it alive indefinitely.

### Can django-rest-knox log out all devices at once?

Yes. Because Knox issues one token per call to the login view, each client has its own token, and the library provides an option for a logged-in client to remove all the tokens the server has, forcing all clients to re-authenticate.

### How do I run the django-rest-knox test suite locally?

With Docker installed, run the `./docker-run-tests.sh` script, which runs the test suite in every Python and Django version. You can also run regular tox in the root folder, though the project notes that makes testing the version matrix a bit more tricky.

## Sources

- [Issues](https://github.com/jazzband/django-rest-knox/issues)
- [jazzband/django-rest-knox on GitHub](https://github.com/jazzband/django-rest-knox)
- [License: MIT](https://github.com/jazzband/django-rest-knox/blob/develop/LICENSE)
- [README](https://github.com/jazzband/django-rest-knox/blob/develop/README.md)
- [Releases](https://github.com/jazzband/django-rest-knox/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jazzband-django-rest-knox
