In Django REST Framework, how do you run TokenAuthentication for a mobile app beside SessionAuthentication for the web app, and why is CSRF session-only?
answer
- two classes in one list
- authtoken app and migration
- cookies are sent automatically
- DRF views are csrf_exempt
basics
~10 sList TokenAuthentication and SessionAuthentication in DEFAULT_AUTHENTICATION_CLASSES and add rest_framework.authtoken. Only SessionAuthentication enforces CSRF, because browsers attach session cookies automatically, while a token in the Authorization header must be added by the client.
solid answer
~40 sI add `rest_framework.authtoken` to `INSTALLED_APPS`, migrate, and list both `TokenAuthentication` and `SessionAuthentication` in `DEFAULT_AUTHENTICATION_CLASSES`. The mobile app obtains a key from `obtain_auth_token` and sends `Authorization: Token <key>`; the web app keeps using Django's login and session cookie. DRF wraps its views in `csrf_exempt`, then `SessionAuthentication` re-runs Django's CSRF check itself, but only once it has found an active session user; a failure is a 403 with a "CSRF Failed" detail. Cookies are ambient credentials a hostile page can make the browser send; an `Authorization` header is not, so token requests need no CSRF token. One consequence: anonymous requests skip the check, so a DRF login endpoint is not CSRF-protected.
code
python · 24 lines# settings.py
INSTALLED_APPS = [
# ...
"rest_framework",
"rest_framework.authtoken",
]
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
}
# urls.py
from django.urls import path
from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
path("api/mobile/token/", obtain_auth_token),
]go deeper
Know the two setting entries, the authtoken app, and the header format Authorization: Token <key>.
Explain csrf_exempt on DRF views, when SessionAuthentication re-applies CSRF, and why header tokens are immune to cross-site forgery.
Spot the anonymous login endpoint without CSRF, order the classes deliberately, and test with enforce_csrf_checks=True.
Choose the credential per client type, cookies for the first-party browser app and explicit tokens for native clients, and document the resulting threat model.
## The setup A product has a **web app** served by Django, where users log in through Django's login view and the browser holds a session cookie, and a **mobile app** that cannot share that session. Django REST Framework (DRF) serves both from the same API by listing two authentication classes. ## Wiring token authentication 1. Add `"rest_framework.authtoken"` to `INSTALLED_APPS` and run `manage.py migrate`; this creates the `Token` table. 2. Add `TokenAuthentication` to `DEFAULT_AUTHENTICATION_CLASSES` (or to the views that need it). 3. Expose a way to obtain a token. DRF ships `rest_framework.authtoken.views.obtain_auth_token`: POST a username and password, receive `{"token": "<key>"}`. Operators can also create one with the `drf_create_token` management command. 4. The mobile client sends the key on every request as `Authorization: Token <key>`. To accept `Bearer` instead, subclass `TokenAuthentication` and set its `keyword` attribute. ## How session authentication works in DRF `SessionAuthentication` does no cookie parsing of its own. It reads the user that Django's `SessionMiddleware` and `AuthenticationMiddleware` already attached to the underlying request. If there is no active user, it returns `None` and the next class is tried. If there is one, it **enforces CSRF** before accepting the request. ## Why CSRF applies only to the session path Cross-site request forgery exploits **ambient credentials**: a browser attaches cookies automatically to any request to your domain, including one triggered by a hostile page. A session cookie is exactly such a credential. A token in an `Authorization` header is not — the browser never adds it on its own; the client code must. DRF's design follows from that: - `APIView.as_view()` wraps every DRF view in Django's `csrf_exempt`, so Django's CSRF middleware does **not** check API requests. - `SessionAuthentication.enforce_csrf()` re-runs Django's CSRF check itself, using a subclass of `CsrfViewMiddleware`, but **only after** it has found a session user. - Token, basic and custom header-based classes never call it. The resulting behaviour: | Request | CSRF checked? | Outcome if the CSRF token is missing | |---|---|---| | Browser POST with a logged-in session | yes | `PermissionDenied` — 403 with detail "CSRF Failed: …" | | Browser GET with a logged-in session | Django's check skips safe methods | allowed | | Mobile POST with `Authorization: Token …` | no | allowed, if the token is valid | | Anonymous POST with no session | no | continues as anonymous | The web app must therefore send the CSRF token (usually read from the `csrftoken` cookie into an `X-CSRFToken` header) on unsafe methods. How Django issues and validates that token is Django's CSRF machinery, covered on its own. ## Two traps - **Anonymous requests skip CSRF.** A DRF endpoint that *logs a user in* is called anonymously, so `SessionAuthentication` never enforces CSRF on it — which leaves login CSRF open. DRF's documentation says to use Django's standard login view for session login rather than a hand-rolled API endpoint. - **Order matters for failures.** Classes run in list order and the first result wins. If `TokenAuthentication` comes first and the mobile client sends a garbled header, `AuthenticationFailed` ends the loop immediately — no fallback to the session. If `SessionAuthentication` comes first, a browser with a session is identified before any header is read. The first class listed also decides whether unauthenticated denials are 401 or 403. ## Choosing the order For this mixed setup, both orders work for well-formed requests, because each class returns `None` when its own credential is absent. The order only matters at the edges: | Order | Browser with session and a stray bad `Authorization` header | Anonymous denial status | |---|---|---| | Session first | identified by the session | 403 | | Token first | rejected by the token class | 401 with `WWW-Authenticate: Token` | Mobile clients usually want the 401, so API-first projects put the token class first; browser-first projects often keep the session class first. ## What the built-in token does not give you The `authtoken` model holds **one token per user**, generated randomly and stored as the table's primary key, with a `created` timestamp and no expiry. `obtain_auth_token` uses `get_or_create`, so every device receives the same key, and deleting it logs all of them out. For a single first-party mobile app that is often acceptable; per-device tokens, expiry and JWTs come from custom classes or third-party packages that plug into the same `DEFAULT_AUTHENTICATION_CLASSES` list.
- Why is a DRF login endpoint that uses SessionAuthentication not protected against CSRF?`SessionAuthentication` only enforces CSRF after it finds an authenticated session user, and a login request is anonymous by definition, so the check never runs. Because DRF views are also `csrf_exempt` for Django's middleware, nothing else checks either. DRF's docs recommend Django's standard login view for session login; a custom endpoint must apply CSRF protection explicitly.
- How do you test DRF's session CSRF behaviour, given that the test client skips CSRF checks?Create `APIClient(enforce_csrf_checks=True)`, log in with `force_login()`, and send an unsafe request without the token; expect 403 with a "CSRF Failed" detail. Without that flag the client marks requests as exempt from CSRF enforcement, so a test would pass even though real browsers would be rejected.
A session cookie is a building badge clipped to your coat, shown at every door whether you meant to or not, so the guard also asks for today's passphrase; a token is a key you deliberately take out of your pocket, so nobody can use it on your behalf just by steering you through a door.
saying these in an interview costs you the question
- Token requests need a CSRF token because every POST does.
- DRF relies on Django's CSRF middleware to check API views.
- SessionAuthentication enforces CSRF on anonymous requests as well.
- TokenAuthentication works once listed, without the authtoken app or migrations.
- A CSRF failure under SessionAuthentication returns 401 Unauthorized.