skip to content

Auth & Throttle Policies

DRF checks every request against authentication, permission and throttle classes set globally or per view. Interviewers ask why an endpoint was open by default and what a throttle cannot stop.

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

explore

questions

13

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, which permission applies to a view when none is configured, and how do you require authentication across the whole API?

level: juniorimportance: must knowfreq 68%

basics

~10 s

DRF's default permission is AllowAny, so an unconfigured view accepts anonymous requests. Set DEFAULT_PERMISSION_CLASSES to IsAuthenticated in REST_FRAMEWORK; a view's own permission_classes then replaces that list rather than adding to it.

open as a page

In Django REST Framework, how do you switch on AnonRateThrottle and UserRateThrottle, and what does a throttled client receive back?

level: juniorimportance: must knowfreq 55%

basics

~20 s

DRF throttles nothing by default: list the classes in DEFAULT_THROTTLE_CLASSES and give 'anon' and 'user' rates like '100/day' in DEFAULT_THROTTLE_RATES. A throttled request gets 429 with a Retry-After header and a 'Request was throttled.' detail.

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

In Django REST Framework, how do has_permission and has_object_permission differ, and when does DRF call each of them?

level: middleimportance: must knowfreq 62%

basics

~10 s

has_permission runs on every request before the handler and sees only the request and view; has_object_permission sees a model instance and runs only when check_object_permissions() is called, which generic views do inside get_object().

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

In Django REST Framework, what exactly do IsAdminUser, IsAuthenticatedOrReadOnly and DjangoModelPermissions check before letting a request through?

level: middleimportance: should knowfreq 44%

basics

~10 s

IsAdminUser requires user.is_staff; IsAuthenticatedOrReadOnly lets anyone use GET, HEAD and OPTIONS but requires login for writes; DjangoModelPermissions requires login plus the add, change or delete model permission matching the HTTP method.

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

A DRF ModelViewSet guards documents with an owner-only has_object_permission, yet its list endpoint returns every user's documents — why, and how do you close the gap?

level: seniorimportance: should knowfreq 48%

basics

~10 s

DRF applies object permissions only through get_object(), which list and create never call. Scope get_queryset() to request.user so lists and lookups see only owned rows, and set the owner server-side in perform_create().

open as a page

Why does Django REST Framework's own documentation say its throttling is not a security control, and how does the configured cache backend affect the limits it enforces?

level: seniorimportance: should knowfreq 34%

basics

~20 s

DRF stores each client's request timestamps in Django's cache with a non-atomic read and write, so concurrent requests slip through, a per-process LocMemCache multiplies the limit by the worker count, and IP identities can be spoofed.

open as a page

Behind a reverse proxy, a DRF search endpoint's AnonRateThrottle either lumps all clients into one bucket or is dodged with forged X-Forwarded-For headers — what does NUM_PROXIES change?

level: seniorimportance: should knowfreq 38%

basics

~20 s

DRF identifies anonymous clients in BaseThrottle.get_ident(). With NUM_PROXIES unset it uses the whole X-Forwarded-For string, which clients can vary; set it to your proxy count so DRF takes the address that many entries from the right.

open as a page

In Django REST Framework, how do the &, | and ~ operators compose permission classes, and how can | quietly weaken an owner-only object check?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

DRF's &, | and ~ combine permission classes into one, applying the logic to both hooks. | weakens object checks when an operand inherits BasePermission's has_object_permission, which returns True: IsAuthenticated | IsOwner lets any signed-in user edit.

open as a page