Skip to content

Auth, Permissions & Throttling

DRF executes three security layers in sequence during the dispatch() phase of every request: authentication → permissions → throttling, before handing the request off to a view method. All three components support both global configuration and per-view overrides, and each can be customized with your own implementation.

Authentication

Authentication is responsible for identifying “who sent this request” and assigning the result to request.user and request.auth. A failed authentication does not immediately reject the request — that is the responsibility of the permissions layer. Instead, the user is marked as an anonymous user (AnonymousUser).

Built-in Authentication Classes

ClassDescription
SessionAuthenticationDjango session-based, suitable for browser clients (includes CSRF checks)
BasicAuthenticationHTTP Basic Auth with Base64-encoded username/password; for development only
TokenAuthenticationToken-based authentication; requires the rest_framework.authtoken app
RemoteUserAuthenticationDelegates to Django’s REMOTE_USER middleware

Global Configuration

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

Per-View Override

from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.views import APIView

class MyAPIView(APIView):
    authentication_classes = [SessionAuthentication, BasicAuthentication]

Custom Authentication Class

Inherit from BaseAuthentication and implement the authenticate() method. The method should return a (user, auth) tuple, raise AuthenticationFailed on failure, or return None to pass control to the next authenticator.

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from django.contrib.auth.models import User


class TokenHeaderAuthentication(BaseAuthentication):
    """Read a token from the custom X-Token request header and look up the user."""

    def authenticate(self, request):
        token = request.META.get("HTTP_X_TOKEN")
        if not token:
            return None  # No token present; skip this authenticator

        try:
            user = User.objects.get(profile__token=token)
        except User.DoesNotExist:
            raise AuthenticationFailed("Invalid token")

        return (user, token)  # request.user = user, request.auth = token

Register the class in settings or in a view’s authentication_classes for it to take effect.

Token Authentication Quick Setup

DRF ships with a built-in token authentication scheme suited for decoupled front-end/back-end projects:

# settings.py
INSTALLED_APPS = [
    ...
    'rest_framework.authtoken',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ]
}
python manage.py migrate   # Creates the authtoken table

Generate a token for a user:

from rest_framework.authtoken.models import Token
token, created = Token.objects.get_or_create(user=user)
print(token.key)

The client sends the token in the request header:

Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
Production projects typically replace the built-in token with JWT (djangorestframework-simplejwt), which supports stateless operation, automatic expiry, and token refresh.

Permissions

Permissions determine “whether this user is entitled to access this endpoint.” After authentication establishes the user’s identity, permission classes enforce access control.

Built-in Permission Classes

ClassDescription
AllowAnyAllow access to everyone (default)
IsAuthenticatedOnly authenticated users may access
IsAdminUserOnly admin users (is_staff=True) may access
IsAuthenticatedOrReadOnlyAuthenticated users can read and write; anonymous users are read-only (GET/HEAD/OPTIONS)

Global Permission Configuration

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}

Per-View Permission Override

from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView

class StudentAPIView(APIView):
    permission_classes = [IsAuthenticated]

Custom Permission Class

Inherit from BasePermission and implement one or both of the following methods:

  • has_permission(request, view) — controls access to the view itself (list level).
  • has_object_permission(request, view, obj) — controls access to a specific object (detail level; only triggered when the view calls get_object()).
from rest_framework.permissions import BasePermission, SAFE_METHODS


class IsOwnerOrReadOnly(BasePermission):
    """Only the resource owner may modify or delete; everyone else is read-only."""

    def has_object_permission(self, request, view, obj):
        # Safe methods (GET, HEAD, OPTIONS) are allowed for everyone
        if request.method in SAFE_METHODS:
            return True
        # Write operations are restricted to the resource creator
        return obj.owner == request.user


class IsStaffOrCreator(BasePermission):
    """Admins or creators may write; everyone else is read-only."""

    def has_permission(self, request, view):
        return bool(request.user and request.user.is_authenticated)

    def has_object_permission(self, request, view, obj):
        if request.method in SAFE_METHODS:
            return True
        return request.user.is_staff or obj.created_by == request.user

Multiple permission classes are evaluated with AND logic — all classes must return True for the request to proceed:

permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]

Throttling

Throttling controls the rate at which an endpoint can be accessed, preventing abuse such as brute-force attacks, API scraping, or high-concurrency floods.

Built-in Throttle Classes

ClassDescription
AnonRateThrottleAnonymous users, identified by IP address; uses the anon rate
UserRateThrottleAuthenticated users, identified by user ID; uses the user rate
ScopedRateThrottleApplies a named scope (throttle_scope) to configure per-view rates

Global Throttle Configuration

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',    # Anonymous users: max 100 requests per day
        'user': '1000/day',   # Authenticated users: max 1000 requests per day
    }
}

Rate format: count/period, where period may be second (s), minute (m), hour (h), or day (d).

Per-View Throttle Configuration

from rest_framework.throttling import UserRateThrottle
from rest_framework.views import APIView

class LoginView(APIView):
    throttle_classes = [UserRateThrottle]

Per-View Scoped Throttling (ScopedRateThrottle)

Use ScopedRateThrottle when different endpoints need different rate limits:

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': ['rest_framework.throttling.ScopedRateThrottle'],
    'DEFAULT_THROTTLE_RATES': {
        'login':    '5/minute',   # Login endpoint: max 5 per minute
        'contacts': '100/hour',   # Contacts endpoint: max 100 per hour
    }
}
class LoginView(APIView):
    throttle_scope = 'login'

class ContactListView(ListAPIView):
    throttle_scope = 'contacts'

Custom Throttle Class

from rest_framework.throttling import SimpleRateThrottle


class IPRateThrottle(SimpleRateThrottle):
    """IP-based throttle; rate is read from the `ip` key in settings."""
    scope = 'ip'

    def get_cache_key(self, request, view):
        return self.cache_format % {
            'scope': self.scope,
            'ident': self.get_ident(request),  # Retrieve the client IP
        }

How the Three Layers Work Together

HTTP Request
dispatch()
    ├─ Authentication (authentication_classes)
    │    → Sets request.user / request.auth
    ├─ Permissions (permission_classes)
    │    → has_permission() passes? Otherwise 403
    ├─ Throttling (throttle_classes)
    │    → allow_request() passes? Otherwise 429
View method (get / post / put / delete ...)
    └─ Detail operations: get_object() → has_object_permission()
has_object_permission() is only triggered when the view calls self.get_object(). Querying data directly through the ORM bypasses object-level permission checks entirely.
Last updated on