DRF Authentication & Permissions
Reviewed & published by Brayan K
In Django REST Framework, authentication identifies the user behind a request from its credentials, and permissions then decide whether that user is allowed to perform the requested action.
Part of the free Django course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll set authentication_classes (session, basic, and token), obtain and verify a token, control access with permission_classes like IsAuthenticated and IsAuthenticatedOrReadOnly, write a custom BasePermission, and add throttling to rate-limit clients.
What You'll Learn in This Lesson
- Set authentication_classes on a view
- How TokenAuthentication checks the header
- Obtain a token from username and password
- Use IsAuthenticated and IsAuthenticatedOrReadOnly
- Write a custom BasePermission (object ownership)
- Rate-limit clients with throttling (429)
1 Authentication and TokenAuthentication
A DRF view's authentication_classes list says how to identify the caller. SessionAuthentication uses the browser cookie, BasicAuthentication uses username and password, and TokenAuthentication reads an Authorization: Token <key> header and looks the key up to find the user.
# api/views.py
from rest_framework.authentication import TokenAuthentication, SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from rest_framework.response import Response
class ProfileView(APIView):
authentication_classes = [TokenAuthentication, SessionAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request):
# request.user is set by the authentication class
return Response({"username": request.user.username})
# To enable tokens, add to INSTALLED_APPS:
# "rest_framework.authtoken"
# then run: python manage.py migrate# TokenAuthentication: the client sends "Authorization: Token <key>". DRF looks
# the key up in a token table to find the user. We model that lookup here: a
# valid token resolves to a user; anything else means "not authenticated".
token_table = {
"abc123": "ada",
"def456": "ben",
}
def authenticate(authorization_header):
# Header looks like: "Token abc123"
if not authorization_header:
return None # no credentials -> anonymous
scheme, _, key = authorization_header.partition(" ")
if scheme != "Token":
return None
return token_table.get(key) # username or None
print("valid token:", authenticate("Token abc123"))
print("other user:", authenticate("Token def456"))
print("bad token:", authenticate("Token wrong"))
print("no header:", authenticate(""))
print("wrong scheme:", authenticate("Bearer abc123"))
# EXPECTED:
# valid token: ada
# other user: ben
# bad token: None
# no header: None
# wrong scheme: None
# ✅ Expected output:
# valid token: ada
# other user: ben
# bad token: None
# no header: None
# wrong scheme: None2 Permissions: IsAuthenticated and ReadOnly
Once the user is known, permission_classes decide if the request may proceed. IsAuthenticated blocks anonymous users entirely. IsAuthenticatedOrReadOnly lets anyone perform safe read methods (GET, HEAD, OPTIONS) but requires login for writes.
# api/views.py
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework import generics
from .models import Article
from .serializers import ArticleSerializer
class ArticleListCreate(generics.ListCreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# anyone can GET the list; only logged-in users can POST a new one
permission_classes = [IsAuthenticatedOrReadOnly]To obtain a token in the first place, wire up DRF's built-in view:
# project/urls.py
from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
path("api-token-auth/", obtain_auth_token), # POST username + password
]
# Response: {"token": "abc123..."} -> client stores and resends it# permission_classes decide if an authenticated (or anonymous) request may
# proceed. IsAuthenticated blocks anonymous users. IsAuthenticatedOrReadOnly
# allows anyone to GET/HEAD but requires login to POST/PUT/DELETE.
SAFE_METHODS = ("GET", "HEAD", "OPTIONS")
def is_authenticated(user, method):
# require a logged-in user for every method
return user is not None
def is_authenticated_or_read_only(user, method):
# safe (read) methods are always allowed
if method in SAFE_METHODS:
return True
return user is not None
# (user, method) pairs to test
cases = [
("ada", "GET"),
(None, "GET"),
("ada", "POST"),
(None, "POST"),
]
print("IsAuthenticated:")
for user, method in cases:
print(f" {user!r:8} {method:5} -> {is_authenticated(user, method)}")
print("IsAuthenticatedOrReadOnly:")
for user, method in cases:
print(f" {user!r:8} {method:5} -> {is_authenticated_or_read_only(user, method)}")
# EXPECTED:
# IsAuthenticated:
# 'ada' GET -> True
# None GET -> False
# 'ada' POST -> True
# None POST -> False
# IsAuthenticatedOrReadOnly:
# 'ada' GET -> True
# None GET -> True
# 'ada' POST -> True
# None POST -> False
# ✅ Expected output:
# IsAuthenticated:
# 'ada' GET -> True
# None GET -> False
# 'ada' POST -> True
# None POST -> False
# IsAuthenticatedOrReadOnly:
# 'ada' GET -> True
# None GET -> True
# 'ada' POST -> True
# None POST -> False3 Custom Permissions and Throttling
When the built-ins aren't enough, subclass BasePermission and implement has_object_permission — for example, to allow editing only by the object's owner. Separately, throttling caps how many requests a client may make, returning 429 Too Many Requests past the limit.
# api/permissions.py
from rest_framework.permissions import BasePermission, SAFE_METHODS
class IsOwnerOrReadOnly(BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True # anyone may read
return obj.owner == request.user # only the owner may edit
# settings.py — throttle anonymous and authenticated clients differently
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"anon": "100/day",
"user": "1000/day",
},
}# A custom BasePermission checks something specific — here, that a request's
# user owns the object. has_object_permission returns True/False. We also add
# a simple rate throttle that allows N requests then returns 429.
class IsOwner:
# mirrors BasePermission.has_object_permission(self, request, view, obj)
def has_object_permission(self, user, obj):
return obj["owner"] == user
class RateThrottle:
def __init__(self, limit):
self.limit = limit
self.hits = 0
def allow(self):
self.hits += 1
return self.hits <= self.limit # True until the limit is exceeded
perm = IsOwner()
article = {"id": 1, "owner": "ada", "title": "Hello"}
print("ada can edit:", perm.has_object_permission("ada", article))
print("ben can edit:", perm.has_object_permission("ben", article))
throttle = RateThrottle(limit=3)
results = [throttle.allow() for _ in range(5)]
print("throttle (limit 3):", results)
print("status of 4th request:", 200 if results[3] else 429)
# EXPECTED:
# ada can edit: True
# ben can edit: False
# throttle (limit 3): [True, True, True, False, False]
# status of 4th request: 429
# ✅ Expected output:
# ada can edit: True
# ben can edit: False
# throttle (limit 3): [True, True, True, False, False]
# status of 4th request: 429🎯 Your Turn: Reject Anonymous Requests
IsAuthenticated must let logged-in users through but deny anonymous ones. Fill in the blank so a None user is rejected.
# 🎯 YOUR TURN: IsAuthenticated must REJECT anonymous requests.
# A request is authenticated only if user is not None.
# Replace ___ so anonymous (None) users are denied.
def has_permission(user):
return user is not ___
print("ada:", has_permission("ada"))
print("anonymous:", has_permission(None))
# EXPECTED OUTPUT:
# ada: True
# anonymous: FalseCommon Errors (And How to Fix Them)
❌ 401 Unauthorized even with a token
The header is malformed — usually missing the Token scheme word before the key.
✅ Fix: send Authorization: Token <key> exactly, and add rest_framework.authtoken to INSTALLED_APPS, then migrate.
❌ 403 Forbidden where you expected 401
The user is authenticated but lacks permission — a different failure from being unauthenticated.
✅ Fix: check your permission_classes; 401 means "log in", 403 means "logged in but not allowed".
❌ Custom permission never blocks anyone
You implemented has_permission but the check is per-object, so it should be has_object_permission.
✅ Fix: put object-level checks in has_object_permission(self, request, view, obj).
📋 Quick Reference
| Class / Setting | Role |
|---|---|
| TokenAuthentication | Identify a user from a token header |
| IsAuthenticated | Require a logged-in user |
| IsAuthenticatedOrReadOnly | Open reads, authenticated writes |
| BasePermission | Base class for custom rules |
| DEFAULT_THROTTLE_RATES | Set request limits (returns 429) |
🏆 Mini Challenge: A Full Request Gate
Combine all three layers into one gate: authenticate the token, throttle the request, then apply an ownership permission — returning the right HTTP status for each outcome.
# 🏆 Mini Challenge: a full request gate combining authentication, permission,
# and throttling. A request must (1) carry a valid token, (2) own the object
# for unsafe methods, and (3) stay under the rate limit.
SAFE_METHODS = ("GET", "HEAD", "OPTIONS")
tokens = {"abc123": "ada", "def456": "ben"}
def gate(auth_header, method, obj, throttle_state, limit=3):
# 1) authenticate
scheme, _, key = auth_header.partition(" ")
user = tokens.get(key) if scheme == "Token" else None
# 2) throttle (count every request that gets this far)
throttle_state["hits"] += 1
if throttle_state["hits"] > limit:
return "429 Too Many Requests"
# 3) permission: read is open to anyone; writes need ownership
if method in SAFE_METHODS:
return "200 OK" if user else "200 OK (anonymous read)"
if user is None:
return "401 Unauthorized"
if obj["owner"] != user:
return "403 Forbidden"
return "200 OK"
state = {"hits": 0}
article = {"owner": "ada"}
print(gate("Token abc123", "GET", article, state)) # owner reads
print(gate("Token def456", "POST", article, state)) # ben edits ada's -> 403
print(gate("", "POST", article, state)) # anonymous write -> 401
print(gate("Token abc123", "PUT", article, state)) # 4th hit -> throttled
# EXPECTED:
# 200 OK
# 403 Forbidden
# 401 Unauthorized
# 429 Too Many Requests❓ Frequently Asked Questions
Lesson complete — your API is secured!
You can set authentication_classes, verify a token, obtain one from username and password, gate views with IsAuthenticated and IsAuthenticatedOrReadOnly, write a custom BasePermission, and throttle clients with a 429.
Practice quiz
What question does authentication answer in DRF?
- Are you allowed to do this?
- Who are you?
- How fast are you going?
- Which view handles this?
Answer: Who are you?. Authentication identifies the user behind a request from its credentials.
What question do permissions answer?
- Are you allowed to do this?
- Who are you?
- What format is the body?
- Which database to use?
Answer: Are you allowed to do this?. Permissions decide whether the identified user may perform the requested action.
What HTTP header format does TokenAuthentication expect?
- Authorization: Bearer <key>
- X-Api-Key: <key>
- Authorization: Token <key>
- Cookie: token=<key>
Answer: Authorization: Token <key>. TokenAuthentication reads the header 'Authorization: Token <key>'.
Which permission class allows reads for anyone but writes only for logged-in users?
- IsAdminUser
- AllowAny
- IsAuthenticated
- IsAuthenticatedOrReadOnly
Answer: IsAuthenticatedOrReadOnly. IsAuthenticatedOrReadOnly permits safe methods for all but requires auth for unsafe methods.
Which attribute on request is set by authentication before permissions run?
- request.user
- request.token
- request.auth_id
- request.session_key
Answer: request.user. Authentication runs first and sets request.user, which permission classes then check.
Which permission class requires a logged-in user for every request?
- AllowAny
- IsAuthenticated
- IsAuthenticatedOrReadOnly
- DjangoModelPermissions
Answer: IsAuthenticated. IsAuthenticated blocks anonymous users from all methods.
To write a custom object-level permission, which base class do you subclass?
- models.Model
- BaseSerializer
- BasePermission
- APIView
Answer: BasePermission. Subclass BasePermission and implement has_object_permission for object-level rules.
What status code does DRF return when a request exceeds the throttle limit?
- 403 Forbidden
- 401 Unauthorized
- 400 Bad Request
- 429 Too Many Requests
Answer: 429 Too Many Requests. Throttling returns 429 Too Many Requests once the rate limit is exceeded.
Which throttle classes limit anonymous vs authenticated users?
- AnonRateThrottle and UserRateThrottle
- LoginThrottle and LogoutThrottle
- ReadThrottle and WriteThrottle
- DayThrottle and HourThrottle
Answer: AnonRateThrottle and UserRateThrottle. DRF ships AnonRateThrottle and UserRateThrottle, configured with rates like 100/day and 1000/day.
How does a client typically obtain a token to use with TokenAuthentication?
- By reading it from the admin page
- By POSTing username and password to obtain_auth_token
- By generating it client-side
- By guessing the user id
Answer: By POSTing username and password to obtain_auth_token. DRF's obtain_auth_token view returns a token when you POST valid credentials.
Continue this course
- Previous: Custom Management Commands
- Next: Deployment: WSGI, Static & Settings — Ship to production with DEBUG=False, collectstatic, Gunicorn, and env settings
- Quick reference: Django cheat sheet
Frequently asked questions
What is the difference between authentication and permissions in DRF?
Authentication answers the question who are you by identifying the user behind a request from its credentials, such as a token or a session cookie. Permissions answer the question are you allowed to do this by deciding whether that identified user may perform the requested action. Authentication runs first and sets request.user; permission classes then run and either let the request through or return a 401 or 403 response.
How does TokenAuthentication work and how do I get a token?
With TokenAuthentication the client sends an HTTP header of the form Authorization: Token <key>, and DRF looks that key up in the authtoken_token table to find the matching user. You obtain a token by wiring up DRF's obtain_auth_token view at a URL, then POSTing a username and password to it; the response contains the token string the client stores and sends on every later request.
When should I use IsAuthenticatedOrReadOnly?
Use IsAuthenticatedOrReadOnly when you want a public API that anyone can read but only logged-in users can change. It allows the safe HTTP methods GET, HEAD, and OPTIONS for everyone, including anonymous visitors, while requiring authentication for the unsafe methods POST, PUT, PATCH, and DELETE. It is a common default for content APIs where browsing is open but editing is restricted.
What is throttling and why would I add it?
Throttling limits how many requests a client may make in a given time window, returning a 429 Too Many Requests response once the limit is exceeded. You add it to protect your API from abuse, accidental request floods, and brute-force attempts on login endpoints. DRF ships with rate classes like AnonRateThrottle and UserRateThrottle, configured with rates such as 100/day for anonymous users and 1000/day for authenticated ones.