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

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: None

2 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  -> False

3 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: False

Common 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 / SettingRole
TokenAuthenticationIdentify a user from a token header
IsAuthenticatedRequire a logged-in user
IsAuthenticatedOrReadOnlyOpen reads, authenticated writes
BasePermissionBase class for custom rules
DEFAULT_THROTTLE_RATESSet 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

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.