skip to content

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.