Web APIs for Django, made easy.
—
Security framework providing authentication backends for user identification and permission classes for access control. Supports multiple authentication methods and granular permission systems.
Authentication classes identify users making requests to the API.
class BaseAuthentication:
"""
Base class for all authentication backends.
"""
def authenticate(self, request):
"""
Authenticate the request and return user/auth tuple.
Args:
request: DRF Request object
Returns:
tuple or None: (user, auth) tuple or None if not authenticated
"""
def authenticate_header(self, request):
"""
Return string for WWW-Authenticate header when authentication fails.
Args:
request: DRF Request object
Returns:
str or None: Authentication header value
"""
class BasicAuthentication(BaseAuthentication):
"""
HTTP Basic authentication using username and password.
"""
www_authenticate_realm = 'api'
def authenticate(self, request):
"""Authenticate using HTTP Basic credentials."""
def authenticate_credentials(self, userid, password, request=None):
"""
Authenticate credentials against user database.
Args:
userid (str): Username
password (str): Password
request: HTTP request
Returns:
tuple: (user, None) if valid
Raises:
AuthenticationFailed: If credentials invalid
"""
class SessionAuthentication(BaseAuthentication):
"""
Django session-based authentication.
"""
def authenticate(self, request):
"""Authenticate using Django session."""
def enforce_csrf(self, request):
"""
Enforce CSRF validation for authenticated requests.
Raises:
PermissionDenied: If CSRF validation fails
"""
class TokenAuthentication(BaseAuthentication):
"""
Token-based authentication using database-stored tokens.
"""
keyword = 'Token' # Authorization header keyword
model = Token # Token model class
def authenticate(self, request):
"""Authenticate using token from Authorization header."""
def authenticate_credentials(self, key):
"""
Authenticate token key against database.
Args:
key (str): Token key
Returns:
tuple: (user, token) if valid
Raises:
AuthenticationFailed: If token invalid
"""
class RemoteUserAuthentication(BaseAuthentication):
"""
Authentication using REMOTE_USER header from web server.
"""
header = 'HTTP_REMOTE_USER' # Header name
def authenticate(self, request):
"""Authenticate using remote user header."""
def clean_username(self, username):
"""
Clean username for user lookup.
Args:
username (str): Raw username
Returns:
str: Cleaned username
"""Permission classes control access to views and objects.
class BasePermission:
"""
Base class for all permission checks with operator support.
"""
def has_permission(self, request, view):
"""
Check if request has permission to access view.
Args:
request: DRF Request object
view: View being accessed
Returns:
bool: True if permission granted
"""
def has_object_permission(self, request, view, obj):
"""
Check if request has permission to access specific object.
Args:
request: DRF Request object
view: View being accessed
obj: Object being accessed
Returns:
bool: True if permission granted
"""
def __and__(self, other):
"""Combine permissions with AND logic."""
return OperandHolder(AND, self, other)
def __or__(self, other):
"""Combine permissions with OR logic."""
return OperandHolder(OR, self, other)
def __invert__(self):
"""Negate permission."""
return SingleOperandHolder(NOT, self)
class AllowAny(BasePermission):
"""
Allow access to any request.
"""
def has_permission(self, request, view):
return True
class IsAuthenticated(BasePermission):
"""
Allow access only to authenticated users.
"""
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)
class IsAdminUser(BasePermission):
"""
Allow access only to admin users.
"""
def has_permission(self, request, view):
return bool(request.user and request.user.is_staff)
class IsAuthenticatedOrReadOnly(BasePermission):
"""
Allow read permissions to any request, write permissions to authenticated users.
"""
def has_permission(self, request, view):
return (
request.method in SAFE_METHODS or
request.user and request.user.is_authenticated
)
class DjangoModelPermissions(BasePermission):
"""
Use Django's built-in model permissions system.
"""
perms_map = {
'GET': [],
'OPTIONS': [],
'HEAD': [],
'POST': ['%(app_label)s.add_%(model_name)s'],
'PUT': ['%(app_label)s.change_%(model_name)s'],
'PATCH': ['%(app_label)s.change_%(model_name)s'],
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
}
def get_required_permissions(self, method, model_cls):
"""
Get required permissions for HTTP method and model.
Args:
method (str): HTTP method
model_cls: Django model class
Returns:
list: Required permission strings
"""
class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions):
"""
Django model permissions with anonymous read access.
"""
class DjangoObjectPermissions(DjangoModelPermissions):
"""
Use Django's object-level permissions.
"""
def has_object_permission(self, request, view, obj):
"""Check object-level permissions using Django's system."""Combine permissions using logical operators.
class OperationHolderMixin:
"""Mixin providing permission combination operators."""
def __and__(self, other):
return OperandHolder(AND, self, other)
def __or__(self, other):
return OperandHolder(OR, self, other)
def __invert__(self):
return SingleOperandHolder(NOT, self)
class SingleOperandHolder(OperationHolderMixin):
"""Hold single operand for unary operations like NOT."""
def __init__(self, operator_class, op1_class):
self.operator_class = operator_class
self.op1_class = op1_class
class OperandHolder(OperationHolderMixin):
"""Hold two operands for binary operations like AND/OR."""
def __init__(self, operator_class, op1_class, op2_class):
self.operator_class = operator_class
self.op1_class = op1_class
self.op2_class = op2_class
class AND:
"""Logical AND operation for permissions."""
def has_permission(self, request, view):
return (
self.op1.has_permission(request, view) and
self.op2.has_permission(request, view)
)
class OR:
"""Logical OR operation for permissions."""
def has_permission(self, request, view):
return (
self.op1.has_permission(request, view) or
self.op2.has_permission(request, view)
)
class NOT:
"""Logical NOT operation for permissions."""
def has_permission(self, request, view):
return not self.op1.has_permission(request, view)# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
# View-level configuration
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
class BookViewSet(ModelViewSet):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
queryset = Book.objects.all()
serializer_class = BookSerializer# Create token for user
from rest_framework.authtoken.models import Token
user = User.objects.get(username='testuser')
token, created = Token.objects.get_or_create(user=user)
print(f"Token: {token.key}")
# Client request with token
import requests
headers = {'Authorization': f'Token {token.key}'}
response = requests.get('http://api.example.com/books/', headers=headers)from rest_framework.permissions import BasePermission
class IsOwnerOrReadOnly(BasePermission):
"""
Custom permission to only allow owners to edit objects.
"""
def has_object_permission(self, request, view, obj):
# Read permissions for any request
if request.method in ['GET', 'HEAD', 'OPTIONS']:
return True
# Write permissions only to owner
return obj.owner == request.user
# Usage in view
class BookViewSet(ModelViewSet):
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
queryset = Book.objects.all()
serializer_class = BookSerializerfrom rest_framework.permissions import IsAuthenticated, IsAdminUser
# Combine permissions with operators
class MyView(APIView):
# Must be admin OR (authenticated AND owner)
permission_classes = [IsAdminUser | (IsAuthenticated & IsOwnerOrReadOnly)]
# Must NOT be anonymous (same as IsAuthenticated)
permission_classes = [~AllowAny]from rest_framework.decorators import api_view, authentication_classes, permission_classes
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
@api_view(['GET'])
@authentication_classes([TokenAuthentication])
@permission_classes([IsAuthenticated])
def protected_view(request):
return Response({'message': f'Hello {request.user.username}!'})from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from django.contrib.auth import get_user_model
User = get_user_model()
class CustomTokenAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.META.get('HTTP_X_API_KEY')
if not token:
return None
try:
user = User.objects.get(api_key=token)
except User.DoesNotExist:
raise AuthenticationFailed('Invalid API key')
return (user, token)Rate limiting classes for controlling request frequency and preventing API abuse. Throttling works alongside authentication and permissions to provide comprehensive access control.
class BaseThrottle:
"""
Base class for all throttling implementations.
"""
def allow_request(self, request, view):
"""
Return True if request should be allowed, False otherwise.
Args:
request: DRF Request object
view: API view being accessed
Returns:
bool: Whether request is allowed
"""
def wait(self):
"""
Return recommended number of seconds to wait before next request.
Returns:
int or None: Seconds to wait, or None if not available
"""
class SimpleRateThrottle(BaseThrottle):
"""
Cache-based rate throttling with configurable rates.
"""
cache = None # Cache backend
timer = None # Time function
rate = None # Rate string like "100/day"
scope = None # Throttle scope for settings lookup
def get_cache_key(self, request, view):
"""
Return unique cache key for throttling.
Must be overridden.
Returns:
str or None: Cache key or None to skip throttling
"""
class AnonRateThrottle(SimpleRateThrottle):
"""
Throttle anonymous requests by IP address.
"""
scope = 'anon'
class UserRateThrottle(SimpleRateThrottle):
"""
Throttle authenticated requests by user.
"""
scope = 'user'
class ScopedRateThrottle(SimpleRateThrottle):
"""
Throttle requests based on view-specific scope.
"""
scope_attr = 'throttle_scope'Configure throttling in Django settings:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '1000/day',
'burst': '60/min',
'sustained': '1000/day'
}
}from rest_framework.throttling import UserRateThrottle, AnonRateThrottle
class BookViewSet(ModelViewSet):
throttle_classes = [UserRateThrottle, AnonRateThrottle]
queryset = Book.objects.all()
serializer_class = BookSerializer
# Custom throttle scope
class ContactViewSet(ModelViewSet):
throttle_classes = [ScopedRateThrottle]
throttle_scope = 'contacts'
queryset = Contact.objects.all()
serializer_class = ContactSerializerfrom rest_framework.throttling import UserRateThrottle
class BurstRateThrottle(UserRateThrottle):
scope = 'burst'
class LoginRateThrottle(AnonRateThrottle):
scope = 'login'
def get_cache_key(self, request, view):
if request.user.is_authenticated:
return None # Only throttle anonymous users
ident = self.get_ident(request)
return self.cache_format % {
'scope': self.scope,
'ident': ident
}from rest_framework.decorators import api_view, throttle_classes
from rest_framework.throttling import UserRateThrottle
@api_view(['POST'])
@throttle_classes([UserRateThrottle])
def send_message(request):
return Response({'message': 'Message sent'})# Safe HTTP methods (read-only)
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
# Utility functions
def get_authorization_header(request):
"""
Extract authorization header from request.
Args:
request: Django request object
Returns:
bytes: Authorization header value
"""Install with Tessl CLI
npx tessl i tessl/pypi-djangorestframework