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:
- Read the
csrftokencookie. - Send it in
X-CSRFTokenheader 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 (orAnonymousUser).request.auth— scheme-specific. ForTokenAuthenticationit’s theTokenrow; 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_tokenfor username/password → token.simplejwt’sTokenObtainPairViewfor JWT.- For session-backed SPAs, post to
django.contrib.auth.views.LoginViewand rely on the session cookie.
Common pitfalls
AllowAnyis the default permission. Authentication alone doesn’t gate access — you also needIsAuthenticated.- Anonymous users get 403 instead of 401 when no auth class implements
authenticate_header(). SessionAuthenticationenforces CSRF for everyone, including token clients, when listed inDEFAULT_AUTHENTICATION_CLASSESand the request happens to be a POST with a session cookie.select_related("user")matters on the token lookup. DefaultTokenAuthenticationalready does it; custom auth classes often forget and add an N+1.
Interview angle
- “Walk me through where authentication happens in DRF.” —
dispatch→initial→perform_authentication→ loops throughauthentication_classesuntil 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?” —
SessionAuthenticationis 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, implementauthenticate()returning(user, auth)orNone, andauthenticate_header()to make 401 responses correct. - “Where’s the difference between
request.userandrequest.auth?” — user is the Django user model; auth is scheme-specific (token row, JWT payload, etc.).