Getting Started
Django REST Framework Simple API Key is fast and secure API Key authentication plugin for REST API built with Django Rest Framework.
Django REST Framework Simple API Key is fast and secure API Key authentication plugin for REST API built with Django Rest Framework.
Introduction
Django REST Simple Api Key is a package built upon Django, Django REST Framework, and the fernet cryptography module to generate, encrypt, and decrypt API keys. It provides fast, secure and customizable API Key authentication.
Benefits
Why should you use this package for your API Key authentication?
-
⚡ Fast: We use the fernet cryptography module to generate, encrypt, and decrypt API keys. Besides the security facade, it is blazing fast allowing you to treat requests quickly and easily.
-
🔐 Secure: Fernet guarantees that a message encrypted using it cannot be manipulated or read without the key, which we call
FERNET_KEY. As long as you treat the fernet key at the same level you treat the DjangoSECRET_KEYsetting, you are good to go. -
🔧 Customizable: The models, authentication backend, and permissions classes can be rewritten and fit your needs. We do our best to extend Django classes and methods, so you can easily extend our classes and methods.😉 Your Api Key authentication settings are kept in a single configuration dictionary named
DRF_API_KEYin thesettings.pyfile of your Django project. It can be customized to fit your project needs.
Quickstart
This walks through everything needed to go from a fresh Django project to a protected endpoint that a real client can call: installing the package, generating and storing the Fernet secret safely, creating a key, calling the API with it, and revoking it.
Install
pip install drf-simple-apikeyRegister the apps
# settings.py
INSTALLED_APPS = [
# ...
"rest_framework",
"drf_simple_apikey",
]Generate the Fernet secret — and keep it out of your codebase
python manage.py generate_fernet_keyThis prints a key such as sVjomf7FFy351xRxDeJWFJAZaE2tG3MTuUv92TLFfOA=. Treat it exactly like your Django SECRET_KEY: put it in an environment variable, never commit it to version control, and never log it. See the Threat Model for why this specific value matters so much.
# .env (make sure .env is in your .gitignore)
DRF_API_KEY_FERNET_SECRET=sVjomf7FFy351xRxDeJWFJAZaE2tG3MTuUv92TLFfOA=# settings.py
import os
DRF_API_KEY = {
"FERNET_SECRET": os.environ["DRF_API_KEY_FERNET_SECRET"],
}Using os.environ[...] (not .get(...)) means your app fails to start with a clear KeyError if the secret isn't set, instead of silently running unauthenticated.
Run migrations
python manage.py migrateProtect a view
Combine an authentication class with a permission class — authentication alone only identifies who is calling; a permission class decides whether they're allowed to proceed. This matters concretely here: a request with no Authorization header at all, or one using a different scheme, isn't treated as an error by APIKeyAuthentication — it's simply not this authenticator's request to handle, so DRF falls through to request.user being AnonymousUser. Without a permission class, that request would proceed unauthenticated. IsActiveEntity below closes that gap: AnonymousUser.is_active is False, so an anonymous request is still rejected.
from rest_framework import viewsets
from rest_framework.response import Response
from drf_simple_apikey.backends import APIKeyAuthentication
from drf_simple_apikey.permissions import IsActiveEntity
class FruitViewSets(viewsets.ViewSet):
http_method_names = ["get"]
authentication_classes = (APIKeyAuthentication,)
permission_classes = (IsActiveEntity,)
def list(self, request):
return Response([{"detail": True}], 200)⚠️ By default, the Django user model (
AUTH_USER_MODEL) is the entity an API key belongs to.
Hand off the key once
The encrypted key is only ever shown at creation time — the database never stores it, so it can't be retrieved later (see Threat Model for why). Create it from a shell, a management command, or your own admin tooling:
from drf_simple_apikey.models import APIKey
api_key, key = APIKey.objects.create_api_key(entity=user)
print(key)
# gAAAAABn... — send this to the integration now, over a channel you
# control (a secrets manager, a one-time-view link, etc). It will
# never be shown again; if it's lost, revoke it and issue a new one.If you create the key through the Django admin instead, the same one-time key is shown as a warning message on the change page right after saving.
Call the API
Send the key in the Authorization header, prefixed with the configured keyword (Api-Key by default — see AUTHENTICATION_KEYWORD_HEADER):
curl https://your-api.example.com/fruits/ \
-H "Authorization: Api-Key gAAAAABn..."request.user and request.auth
Once authentication succeeds, both are populated for the rest of the request:
request.useris the entity the key belongs to — a Django user by default.request.authis theAPIKeymodel instance used to authenticate (not the raw key string), so a view or permission class can readrequest.auth.scopes,request.auth.expiry_date,request.auth.name, etc.
Revoke a key
APIKey.objects.revoke_api_key(api_key.pk)A revoked key immediately fails authentication with "This API Key has been revoked.", regardless of its expiry date.
Rotate the Fernet secret
Revoking is per-key. To change the FERNET_SECRET itself — on a schedule, as a precaution, not as incident response for a suspected leak — see Rotation. If you suspect the secret itself has already leaked, follow the incident response steps instead: rotation intentionally keeps the old secret trusted during its transition window, which is the wrong behavior when that old secret is the one that's compromised.
Common failure responses
Every rejection from this package — a failed authentication and a failed permission check — comes back as 403 Forbidden, not 401 Unauthorized, as long as APIKeyAuthentication is the first authentication class on the view. DRF only returns 401 when the first listed authenticator supplies a WWW-Authenticate header, and this backend deliberately doesn't (authenticate_header() returns None), so don't special-case 401 in client code that talks to this API.
A missing Authorization header, or one using a different scheme (e.g. Bearer ...), doesn't produce a detail message from this backend at all — APIKeyAuthentication simply returns None and defers to whatever's next: another authentication class, if you've configured one (see Authentication), or otherwise an anonymous request.user that your permission classes are responsible for rejecting.
detail message | Cause |
|---|---|
Incorrect API KEY format. | The header uses the configured keyword (Api-Key by default) but doesn't match <AUTHENTICATION_KEYWORD_HEADER> <key> — e.g. the key itself is missing, or contains a stray space. |
Invalid API Key. | The value isn't a valid Fernet token for the configured FERNET_SECRET — wrong secret, corrupted value, or a key issued under a different secret. |
API Key has already expired. | The key's expiry_date is in the past. |
This API Key has been revoked. | The key's revoked flag is True. |
No entity matching this api key. | The underlying APIKey row was deleted after the key was issued. |
Access denied from blacklisted IP. / Access restricted to specific IP addresses. | The caller's IP is blocked by blacklisted_ips/whitelisted_ips. |
API key authentication requires HTTPS... | ENFORCE_HTTPS is on and the request arrived over plain HTTP. |
A permission class's own message (e.g. IsActiveEntity's "Entity is not active.") | Authentication succeeded, but a permission class rejected the request — check permission_classes on the view. |
Security Considerations
Before deploying to production:
- Treat your Fernet key like your Django
SECRET_KEY: environment variables only, never in version control, never in logs. - Always use HTTPS in production: the package can enforce this automatically. See Security for details.
- Review your audit logs: the package logs authentication and revocation events — make sure something is actually watching them.
For the full picture of what this design protects against, read the Threat Model. For feature-by-feature details, see Security.
Changelog
See CHANGELOG.md.
Contributing
See CONTRIBUTING.md.