In Django REST Framework, what do authentication classes do, and what do request.user and request.auth hold after TokenAuthentication succeeds?
answer
- who, not whether
- first non-None tuple wins
- user plus extra credential
- anonymous fallback values
basics
~20 sAuthentication 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 sDRF 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# 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
Recall the difference between authentication and permissions, the default classes, and what request.user and request.auth contain for token authentication.
Explain the loop precisely: None moves on, a tuple wins, AuthenticationFailed stops everything, and the anonymous fallback values come from two settings.
Catch the AllowAny default and the LoginRequiredMiddleware exemption in review, and make sure every endpoint's access rule is explicit.
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.