backend / web frameworks / django / drf / 07_authentication.md

DRF Authentication

5 interview angles 4 min read source

DRF Authentication

Authentication identifies the user. It runs first in the request lifecycle, before permissions and throttles. The result is request.user (a Django User or AnonymousUser) and request.auth (token / scheme-specific data).

Built-in classes

Class Mechanism Typical use
SessionAuthentication Django session cookie SPA on the same origin as Django; admin-style apps
BasicAuthentication HTTP Basic Internal tools, never over plain HTTP
TokenAuthentication DB-backed opaque token in Authorization: Token <key> header Simple API clients
RemoteUserAuthentication REMOTE_USER from upstream proxy Behind SSO/Apache mod_auth
Third-party JWTAuthentication (djangorestframework-simplejwt) Signed token, no DB lookup per request Mobile / SPA / microservices

Configuring globally

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

DRF tries each class in order. The first one that returns (user, auth) wins. If all return None, request.user = AnonymousUser. If one raises AuthenticationFailed, the request fails with 401 immediately.

Per-view override

class WebhookView(APIView):
    authentication_classes = []   # disable auth for webhooks (verify signature instead)
    permission_classes = []

TokenAuthentication setup

INSTALLED_APPS += ["rest_framework.authtoken"]
# manage.py migrate

Tokens are one row per user in authtoken_token. Generate on signup:

from rest_framework.authtoken.models import Token

@receiver(post_save, sender=User)
def create_auth_token(sender, instance, created, **kwargs):
    if created:
        Token.objects.create(user=instance)

Client sends:

Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b

Pros: simple. Cons: every request hits the DB; tokens don’t expire by default; revoking = delete the row.

JWT with simplejwt

# settings.py
INSTALLED_APPS += ["rest_framework_simplejwt"]
REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"] = [
    "rest_framework_simplejwt.authentication.JWTAuthentication",
]

# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns += [
    path("api/token/", TokenObtainPairView.as_view()),
    path("api/token/refresh/", TokenRefreshView.as_view()),
]

Client sends:

Authorization: Bearer eyJ0eXAiOi...

Pros: stateless (no DB lookup per request), short access + long refresh model. Cons: revocation is hard (token stays valid until expiry unless you maintain a blocklist), payload is base64 not encrypted (don’t put secrets in it).

See ../../../11_authentication/ and ../../../25_security/06_jwt_pitfalls.md for deeper JWT notes.

SessionAuthentication and CSRF

SessionAuthentication is the only built-in auth class that enforces CSRF for unsafe methods. If you use it for an SPA, the SPA must:

  1. Read the csrftoken cookie.
  2. Send it in X-CSRFToken header on POST/PUT/PATCH/DELETE.

If you mix SessionAuthentication with token/JWT auth and hit “CSRF Failed: CSRF cookie not set” on a token-auth’d POST, that’s session auth running first. Reorder or remove session auth from DEFAULT_AUTHENTICATION_CLASSES.

Token / JWT auth do not check CSRF (the attacker can’t forge an Authorization header from a victim’s browser without script access).

Custom authentication class

Implement authenticate(request) returning (user, auth) or None:

from rest_framework import authentication, exceptions

class APIKeyAuthentication(authentication.BaseAuthentication):
    keyword = "ApiKey"

    def authenticate(self, request):
        header = authentication.get_authorization_header(request).split()
        if not header or header[0].lower() != self.keyword.lower().encode():
            return None  # not our scheme — let other classes try
        if len(header) != 2:
            raise exceptions.AuthenticationFailed("Invalid header.")
        try:
            key = APIKey.objects.select_related("user").get(key=header[1].decode())
        except APIKey.DoesNotExist:
            raise exceptions.AuthenticationFailed("Invalid key.")
        return (key.user, key)

    def authenticate_header(self, request):
        return self.keyword   # makes 401 responses include WWW-Authenticate: ApiKey

Two-tuple return contract:

  • None = “not my scheme, try the next class.”
  • (user, auth) = “this is the user.”
  • raise AuthenticationFailed = “scheme matched but credentials invalid — stop trying.”

request.user and request.auth

  • request.user — the Django user instance (or AnonymousUser).
  • request.auth — scheme-specific. For TokenAuthentication it’s the Token row; for JWT it’s the decoded payload; for custom classes it’s whatever you returned.

Login/logout views

DRF doesn’t ship login views (it’s stateless by design). Use:

  • rest_framework.authtoken.views.obtain_auth_token for username/password → token.
  • simplejwt’s TokenObtainPairView for JWT.
  • For session-backed SPAs, post to django.contrib.auth.views.LoginView and rely on the session cookie.

Common pitfalls

  • AllowAny is the default permission. Authentication alone doesn’t gate access — you also need IsAuthenticated.
  • Anonymous users get 403 instead of 401 when no auth class implements authenticate_header().
  • SessionAuthentication enforces CSRF for everyone, including token clients, when listed in DEFAULT_AUTHENTICATION_CLASSES and the request happens to be a POST with a session cookie.
  • select_related("user") matters on the token lookup. Default TokenAuthentication already does it; custom auth classes often forget and add an N+1.

Interview angle

  • “Walk me through where authentication happens in DRF.”dispatchinitialperform_authentication → loops through authentication_classes until one returns (user, auth) or raises.
  • “Token vs JWT — when do you pick which?” — Token: fewer moving parts, instant revoke. JWT: stateless, scales without DB lookup, but revocation is awkward and rotation requires refresh tokens.
  • “Why does my POST return ‘CSRF Failed’ even though I’m sending a Bearer token?”SessionAuthentication is in the auth class list and the request also carries a session cookie. Remove session auth or reorder.
  • “How do you build a custom auth scheme?” — subclass BaseAuthentication, implement authenticate() returning (user, auth) or None, and authenticate_header() to make 401 responses correct.
  • “Where’s the difference between request.user and request.auth?” — user is the Django user model; auth is scheme-specific (token row, JWT payload, etc.).