Token authentication in Django REST Framework (DRF) lets a client log in once, receive a secret token, and then prove its identity on every subsequent API request by sending that token in the Authorization header — instead of re-sending a username and password each time. It is the simplest stateless-ish scheme DRF ships with, ideal for internal tools, server-to-server APIs, mobile apps, and prototypes where you do not yet need refresh tokens or third-party OAuth.
This guide shows the full, current setup with Django 5.x, DRF 3.15+, and Python 3.12+, then explains the limitations of DRF's built-in token and compares it against JWT, Knox, OAuth2, and session auth so you can pick the right approach for production.
If you are new to DRF, these companion posts cover the basics first:
How authentication works in DRF (vs. permissions)
DRF separates two concerns that are easy to confuse:
- Authentication answers "Who is making this request?" It runs first, inspects the request (a header, a cookie, a session), and — if it succeeds — sets
request.userandrequest.auth. If it fails,request.userbecomes anAnonymousUser. - Permissions answer "Is this user allowed to do this?" They run after authentication and decide whether to grant or deny access (for example,
IsAuthenticated,IsAdminUser, or your own object-level rules).
Authentication never rejects a request on its own (except for a malformed credential); it just identifies the caller. Permissions do the gatekeeping. With token auth you typically pair TokenAuthentication with the IsAuthenticated permission so only requests carrying a valid token can reach protected endpoints.
Step 1: Install and enable rest_framework.authtoken
DRF's TokenAuthentication is backed by a small Token model that stores one token key per user. Add both rest_framework and rest_framework.authtoken to INSTALLED_APPS:
# settings.py
INSTALLED_APPS = [
# ... your apps ...
"rest_framework",
"rest_framework.authtoken",
]Because authtoken ships its own model, you must apply its migration to create the authtoken_token table:
python manage.py migrateStep 2: Set the default authentication and permission classes
Tell DRF to use TokenAuthentication globally via the DEFAULT_AUTHENTICATION_CLASSES setting. Pairing it with IsAuthenticated means every view requires a valid token unless you override it per-view:
# settings.py
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.TokenAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
}Step 3: Generate a token for a user
A token is created by saving a Token row for a user. You can do this in the Django shell, a data migration, or a management command. The create() call generates a random 40-character key automatically:
from django.contrib.auth import get_user_model
from rest_framework.authtoken.models import Token
User = get_user_model()
user = User.objects.get(username="alice")
# Create a token (raises if one already exists for this user)
token = Token.objects.create(user=user)
print(token.key) # e.g. 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
# Safer when you are not sure whether one exists yet:
token, created = Token.objects.get_or_create(user=user)The original version of this article used
Token.objects.create(user=)— that is invalid Python (an empty keyword argument). Always pass an actual user instance, and preferget_or_createbecause the built-in model allows only one token per user andcreatewill raise anIntegrityErrorif a token already exists.
Step 4: Auto-create a token when a user registers
Manually minting tokens does not scale. Connect a post_save signal to your user model so every newly created user automatically gets a token. Put this in your app's signals.py (or models.py) and make sure the module is imported from your AppConfig.ready():
# accounts/signals.py
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)# accounts/apps.py
from django.apps import AppConfig
class AccountsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "accounts"
def ready(self):
# Importing the module registers the signal receiver
from . import signals # noqa: F401Step 5: Expose a login endpoint with obtain_auth_token
DRF ships a ready-made view, obtain_auth_token, that accepts a POST with username and password and returns the user's token. Wire it into your URLConf.
Note that modern Django uses path() / re_path() — the old django.conf.urls.url() was deprecated in Django 3.x and removed in Django 4.0, so do not use it:
# urls.py
from django.urls import path
from rest_framework.authtoken import views as authtoken_views
urlpatterns = [
path("api/auth-token/", authtoken_views.obtain_auth_token, name="api_token_auth"),
]Request a token by POSTing valid credentials:
curl -X POST http://127.0.0.1:8000/api/auth-token/ \
-H "Content-Type: application/json" \
-d '{"username": "alice", "password": "s3cr3t"}'On success the view returns the token as JSON:
{"token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"}Step 6: Send the token on every request
For all authenticated calls, include the token in the Authorization header. The value must be the literal word Token, a single space, then the key:
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4bcurl http://127.0.0.1:8000/api/profile/ \
-H "Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"DRF reads the header, looks up the matching Token row, and resolves the owning user. When authentication succeeds it populates the request:
request.user→ the authenticatedUserinstancerequest.auth→ therest_framework.authtoken.models.Tokeninstance
If the header is missing or the key is unknown, the user is anonymous and protected endpoints return 401 Unauthorized.
Step 7: Protect a view with IsAuthenticated
With IsAuthenticated set as a default permission, any view is already protected. Here is an explicit example using a class-based and a function-based view so the intent is obvious:
# views.py
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
class ProfileView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
return Response({
"username": request.user.username,
"email": request.user.email,
})
@api_view(["GET"])
@permission_classes([IsAuthenticated])
def whoami(request):
# request.auth is the Token instance for token-authenticated requests
return Response({"user": request.user.username, "token_key": request.auth.key})The reality: DRF's built-in token is simple, but limited
The built-in TokenAuthentication is great for getting started, but it has real constraints you should understand before shipping it to production:
- Tokens never expire. A leaked token is valid forever until you delete it manually.
- One token per user. A user cannot have separate tokens for, say, a phone and a laptop, and rotating one logs out every device.
- Stored in plaintext-equivalent form in the DB. Anyone with read access to
authtoken_tokencan impersonate users. - No refresh mechanism. There is no short-lived/long-lived split, so you trade security (short expiry) against UX (frequent re-login).
- A DB lookup on every request. It is not truly stateless, unlike a signed JWT.
For SPAs, mobile apps, and anything internet-facing, most teams reach for JWT or Knox instead. The table below compares the common options.
DRF auth options compared
| Method | Stateless? | Expiry | Refresh tokens | Multiple devices | Best for |
|---|---|---|---|---|---|
DRF TokenAuthentication |
No (DB lookup) | No (manual) | No | No (one token/user) | Internal tools, prototypes, simple server-to-server APIs |
JWT (djangorestframework-simplejwt) |
Yes (signed) | Yes (built-in) | Yes (access + refresh) | Yes | SPAs and mobile apps that need short-lived access tokens |
Knox (django-rest-knox) |
No (hashed in DB) | Yes (per-token) | No (re-login) | Yes (many tokens/user) | Production token auth with expiry, per-device logout, hashed storage |
OAuth2 (django-oauth-toolkit) |
Depends | Yes | Yes | Yes | Third-party API access, public APIs, scoped/delegated authorization |
| Session auth (DRF built-in) | No (server session) | Yes (session) | n/a | Yes | Browsable API, same-origin web apps using cookies + CSRF |
Rules of thumb: choose JWT when you want stateless, short-lived access tokens with refresh for an SPA or mobile client; choose Knox when you want DRF-style tokens but with expiry, hashed storage, and per-device logout; choose OAuth2 when third parties need scoped access to your API; keep session auth for cookie-based, same-origin web apps. Reserve the built-in TokenAuthentication for internal or low-risk APIs.
Security best practices for token auth
Whatever scheme you pick, the operational rules are similar:
- Serve the API over HTTPS only. A token sent over plain HTTP can be sniffed and replayed. Set
SECURE_SSL_REDIRECT = Trueand HSTS in production. - Give tokens an expiry and rotate them. Built-in DRF tokens do not expire, so either migrate to JWT/Knox or run a scheduled job that prunes old tokens.
- Implement real logout / invalidation. With built-in tokens, logout means deleting the
Tokenrow (request.user.auth_token.delete()). With JWT, use a blocklist for refresh tokens; with Knox, call its logout endpoint. - Never put tokens in URLs or query strings. They end up in server logs, browser history, and referrer headers. Always use the
Authorizationheader. - Be deliberate about client storage. Storing tokens in
localStorageexposes them to XSS; storing them in anHttpOnly,Secure,SameSitecookie mitigates XSS but requires CSRF protection. Pick the trade-off consciously. - Scope and rate-limit. Apply DRF throttling to the login endpoint to slow credential-stuffing, and use permissions/scopes to limit what each token can do.
# A minimal logout view for built-in DRF tokens: delete the user's token
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
@api_view(["POST"])
@permission_classes([IsAuthenticated])
def logout(request):
request.user.auth_token.delete()
return Response(status=status.HTTP_204_NO_CONTENT)Wrapping up
DRF's built-in TokenAuthentication is the fastest way to add authenticated API access in Django: install rest_framework.authtoken, migrate, set your default auth/permission classes, auto-create tokens with a signal, and send Authorization: Token <key> on each request. For production SPAs and mobile apps, graduate to JWT or Knox so you get expiry, refresh, hashed storage, and per-device logout.
At MicroPyramid we have spent 12+ years building and securing Django and DRF APIs across 50+ projects, designing auth flows that hold up in production. If you want a hand hardening your API, see our Django development services and Django REST Framework development services.
Frequently Asked Questions
What is the difference between TokenAuthentication and JWT in DRF?
DRF's built-in TokenAuthentication stores a single random token per user in the database, so every authenticated request triggers a DB lookup and the token never expires on its own. A JWT (typically via djangorestframework-simplejwt) is a signed, self-contained token that DRF can verify without a DB hit, supports built-in expiry, and pairs a short-lived access token with a longer-lived refresh token. Use the built-in token for internal or simple APIs; use JWT for SPAs and mobile apps that need stateless, expiring tokens.
Do DRF tokens expire?
No. Tokens created by DRF's built-in rest_framework.authtoken never expire by default and remain valid until you delete the Token row. If you need expiry, either add a scheduled job that prunes old tokens, or switch to django-rest-knox (per-token expiry) or djangorestframework-simplejwt (built-in access/refresh expiry).
How do I log out or invalidate a DRF token?
For the built-in token, deleting the user's token row invalidates it: request.user.auth_token.delete(). The next request with that key returns 401 Unauthorized. With Knox you call its logout (or logout-all) endpoint to revoke one or all of a user's tokens; with JWT you add the refresh token to a blocklist so it can no longer mint new access tokens.
Why does Token.objects.create(user=user) raise an IntegrityError?
The built-in Token model uses the user as its primary key, so it allows only one token per user. Calling create() for a user who already has a token raises an IntegrityError. Use Token.objects.get_or_create(user=user) to fetch the existing token or create a new one safely.
What should the Authorization header look like?
It must be the word Token, a single space, then the key — for example Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b. The keyword is case-sensitive and the space is required. Never put the token in the URL or query string, where it can leak into logs and browser history.
Is TokenAuthentication secure enough for production?
It can be for internal tools and low-risk, server-to-server APIs served strictly over HTTPS. For public SPAs and mobile apps it is usually not ideal because tokens do not expire, there is only one per user, and they are stored in the database in a directly usable form. For those cases prefer Knox (expiry, hashed storage, per-device logout) or JWT (stateless, short-lived access tokens with refresh).