skip to content

Authenticator Classes

Authentication classes set request.user and request.auth from a session, basic credentials or a token, and SessionAuthentication enforces CSRF. Interviewers probe session vs token auth for SPAs.

part ofDjango REST Frameworkoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In Django REST Framework, what do authentication classes do, and what do request.user and request.auth hold after TokenAuthentication succeeds?

level: juniorimportance: must knowfreq 66%

answer

  1. who, not whether
  2. first non-None tuple wins
  3. user plus extra credential
  4. anonymous fallback values

basics

~20 s

Authentication classes identify the caller; permission classes decide access. DRF tries each class in order, and the first returning (user, auth) sets request.user and request.auth: for TokenAuthentication, the token's user and Token instance; otherwise AnonymousUser and None.

solid answer

~30 s

DRF runs the view's `authentication_classes` — by default `DEFAULT_AUTHENTICATION_CLASSES`, which ships as `SessionAuthentication` then `BasicAuthentication` — in order. Each `authenticate()` returns `None` if its credentials are absent, a `(user, auth)` tuple on success, or raises `AuthenticationFailed` for bad credentials, which stops the loop. The first tuple sets `request.user` and `request.auth`; for `TokenAuthentication` that is `token.user` and the `Token` instance itself. If nothing matches, `request.user` is an `AnonymousUser` and `request.auth` is `None`. Authentication never denies an anonymous caller by itself — with the default `AllowAny` permission the view still runs — so access control needs permission classes.

code

python · 22 lines
python
# settings.py
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.TokenAuthentication",
        "rest_framework.authentication.SessionAuthentication",
    ],
}

# views.py
from rest_framework.response import Response
from rest_framework.views import APIView


class WhoAmI(APIView):
    def get(self, request):
        return Response({
            "user": str(request.user),
            "authenticated": request.user.is_authenticated,
            "scheme": type(request.successful_authenticator).__name__
            if request.successful_authenticator else None,
            "has_token": request.auth is not None,
        })

go deeper

for a junior

Recall the difference between authentication and permissions, the default classes, and what request.user and request.auth contain for token authentication.

for a middle

Explain the loop precisely: None moves on, a tuple wins, AuthenticationFailed stops everything, and the anonymous fallback values come from two settings.

for a senior

Catch the AllowAny default and the LoginRequiredMiddleware exemption in review, and make sure every endpoint's access rule is explicit.

for a principal

Decide which schemes the API accepts, in what order, and how that list is governed across services so clients see consistent behaviour.

## Identification, not authorisation In Django REST Framework (DRF), an **authentication class** answers one question: *who is making this request?* It reads credentials — a session cookie, an `Authorization` header — and, if they are valid, names a user. It does **not** decide whether that user may do anything. That is the job of **permission classes**, which run afterwards and can deny the request. Authentication ends a request itself only when a class **rejects what it found**: bad credentials for its scheme raise `AuthenticationFailed`, and a session user whose request fails the CSRF check gets `PermissionDenied`. Either way the request gets an error response without reaching the view. ## How DRF picks a result Each view has a list of authentication classes — its `authentication_classes` attribute, defaulting to the `DEFAULT_AUTHENTICATION_CLASSES` setting. DRF instantiates them and calls `authenticate(request)` on each in order: 1. A class that finds **no credentials of its kind** returns `None`, and DRF moves on to the next class. 2. A class that **succeeds** returns a two-tuple `(user, auth)`. DRF stops the loop and sets `request.user` and `request.auth` from it. 3. A class that finds **invalid credentials** raises `AuthenticationFailed`. DRF stops the loop, marks the request unauthenticated and re-raises; no later class gets a chance. 4. If **every class returns `None`**, the request is anonymous: `request.user` becomes the value of `UNAUTHENTICATED_USER` (default `django.contrib.auth.models.AnonymousUser`, instantiated) and `request.auth` becomes `UNAUTHENTICATED_TOKEN` (default `None`). The class that succeeded is available as `request.successful_authenticator`. ## What the built-in classes return | Class | Reads | `request.user` | `request.auth` | |---|---|---|---| | `SessionAuthentication` | the user Django's session middleware already attached | that user | `None` | | `BasicAuthentication` | `Authorization: Basic <base64 user:password>` | the user Django's `authenticate()` returns | `None` | | `TokenAuthentication` | `Authorization: Token <key>` | `token.user` | the `Token` model instance | | `RemoteUserAuthentication` | the `REMOTE_USER` value set by the web server | the matching user | `None` | None of them accepts an inactive user: session and remote-user authentication ignore one, while basic and token authentication raise `AuthenticationFailed`. `request.auth` is where a scheme puts **extra authentication context**: for token authentication it is the token row itself, so a view can read `request.auth.created` or compare tokens. ## Configuring it - **Globally**: `REST_FRAMEWORK = {"DEFAULT_AUTHENTICATION_CLASSES": [...]}` in `settings.py`. The shipped default is `SessionAuthentication` followed by `BasicAuthentication`. - **Per class-based view**: set `authentication_classes = [...]` on the view; this **replaces** the global list rather than extending it. - **Per function view**: the `@authentication_classes([...])` decorator, used together with `@api_view`. ## When it runs `request.user` and `request.auth` are **lazy properties**: the loop above runs the first time either is read. DRF does not leave that to chance — `APIView.initial()` calls `perform_authentication()`, which reads `request.user`, before permissions and throttles are checked. The resolved user is also written back to the underlying Django `HttpRequest`, so code outside DRF sees the same user. ## Checking and testing which scheme ran Two tools make authentication observable: - **`request.successful_authenticator`** names the class instance that produced the user, or is `None` for anonymous requests — useful in logging and in a diagnostic endpoint. - **`APIClient.force_authenticate(user=..., token=...)`** from `rest_framework.test` bypasses the classes entirely in tests, setting `request.user` and `request.auth` directly. Use it to test views; test the authentication classes themselves with real headers or sessions, because `force_authenticate` skips exactly the code you would be checking. ## The consequence people miss Because authentication only identifies, an anonymous request is **not rejected** by authentication alone. DRF's default permission class is `AllowAny`, so a view with default settings serves anonymous callers happily: `request.user` is an `AnonymousUser` and the view runs. Protecting an endpoint needs a permission such as `IsAuthenticated`, globally or per view. Django's `LoginRequiredMiddleware` does not fill the gap either: DRF views opt out of it, precisely because DRF handles identification and access inside the view.

  • Does Django's LoginRequiredMiddleware protect DRF endpoints in a Django 5.1+ project?
    No. Since DRF 3.16, `APIView.as_view()` and the ViewSet `as_view()` mark every DRF view with `login_required = False`, opting it out of the middleware. DRF authenticates inside the view, and a login redirect is the wrong response for an API client anyway. Protect endpoints with a permission such as `IsAuthenticated`, globally through the permission defaults or per view.
  • What happens in DRF if the first authentication class raises AuthenticationFailed for a malformed header while a later class would have succeeded?
    The loop stops at the exception: DRF marks the request unauthenticated and re-raises, so later classes never run and the client gets an error response. Returning `None` is the only way to let the next class try. That is why, with `TokenAuthentication` listed before `SessionAuthentication`, a garbled `Authorization: Token` header fails even when the browser also has a valid session.

saying these in an interview costs you the question

  • Authentication classes reject anonymous requests on their own.
  • request.auth holds the user's password hash after Basic authentication.
  • Setting authentication_classes on a view adds to the global list.
  • DRF tries every authentication class and merges their results.
  • request.user is None when no authentication class matches.
open as a page

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?

level: middleimportance: must knowfreq 58%

basics

~10 s

List 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.

open as a page

Why does Django REST Framework answer 403 instead of 401 to unauthenticated requests under its default authentication classes, and how do you change that?

level: middleimportance: should knowfreq 40%

basics

~20 s

DRF asks only the first authentication class for a WWW-Authenticate value. The default list starts with SessionAuthentication, which has none, so DRF turns 401 into 403. Listing TokenAuthentication or BasicAuthentication first, or overriding authenticate_header(), restores 401.

open as a page

DRF's built-in TokenAuthentication keys never expire and are shared across a user's devices; how would you add expiry with a custom authentication class, and what must authenticate() return or raise?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Subclass TokenAuthentication and override authenticate_credentials() to reject tokens whose created time is too old. authenticate() must return None when its credentials are absent, (user, auth) on success, and raise AuthenticationFailed for invalid or expired ones.

open as a page