DRF Permissions and Throttling: API Access Control

Permissions and throttling form the authorization and rate limiting layers protecting REST APIs from unauthorized access and abuse ensuring resources remain available to legitimate users while preventing overload and security breaches. While authentication verifies user identity, permissions determine what authenticated users can do controlling access to specific endpoints, actions, and data based on user roles, object ownership, or custom business logic. Throttling complements permissions by limiting request rates preventing API abuse, denial-of-service attacks, and resource exhaustion ensuring fair usage across all API consumers. Django REST Framework provides built-in permission classes including AllowAny for public endpoints, IsAuthenticated requiring authentication, IsAdminUser restricting to administrators, and IsAuthenticatedOrReadOnly allowing public reads with authenticated writes. Custom permissions enable implementing complex authorization rules checking object ownership, group memberships, subscription tiers, or domain-specific requirements. Throttling mechanisms limit requests per user, per IP address, or globally with configurable rates like 100 requests per hour protecting APIs from excessive usage. This comprehensive guide explores DRF permissions and throttling including understanding permission fundamentals and evaluation flow, implementing built-in permission classes, creating custom object-level permissions, combining multiple permission classes with AND/OR logic, configuring user throttling, anonymous throttling, and scoped throttling, implementing custom throttle classes with Redis caching, handling throttle exceptions and rate limit headers, securing ViewSets with per-action permissions, and best practices for API access control. Mastering permissions and throttling enables building secure scalable APIs maintaining performance under load while enforcing business rules and protecting against malicious users throughout Django REST Framework development integrated with Django security features.
Understanding Permissions
Permissions control authorization determining whether authenticated users can access specific API endpoints or perform particular actions on resources. DRF evaluates permissions after authentication in the request processing pipeline with permission classes returning True to grant access or False to deny it raising PermissionDenied exceptions. Permission checks occur at two levels: view-level permissions checking endpoint access regardless of specific objects, and object-level permissions verifying access to individual resource instances based on ownership or relationships. This two-tier system enables flexible authorization from broad endpoint access control to fine-grained per-object rules. Permissions integrate with Django's permission framework while adding REST-specific checks for API-centric authorization patterns.
# Built-in permission classes
from rest_framework.permissions import (
AllowAny,
IsAuthenticated,
IsAdminUser,
IsAuthenticatedOrReadOnly,
)
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.views import APIView
# 1. AllowAny - Public endpoint
@api_view(['GET'])
@permission_classes([AllowAny])
def public_view(request):
"""Anyone can access this endpoint"""
return Response({'message': 'Public data'})
# 2. IsAuthenticated - Requires authentication
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def protected_view(request):
"""Only authenticated users"""
return Response({'message': f'Hello {request.user.username}'})
# 3. IsAdminUser - Requires admin privileges
@api_view(['GET'])
@permission_classes([IsAdminUser])
def admin_view(request):
"""Only admin users"""
return Response({'message': 'Admin data'})
# 4. IsAuthenticatedOrReadOnly - Read public, write authenticated
@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticatedOrReadOnly])
def article_list(request):
if request.method == 'GET':
# Anyone can read
return Response({'articles': []})
elif request.method == 'POST':
# Only authenticated can write
return Response({'created': True})
# Global permission configuration
# settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
# ViewSet with permissions
from rest_framework import viewsets
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
# GET requests: Anyone
# POST, PUT, PATCH, DELETE: Authenticated users| Permission Class | View Access | Object Access | Common Use Case |
|---|---|---|---|
| AllowAny | Everyone | Everyone | Public endpoints, documentation |
| IsAuthenticated | Authenticated users | Authenticated users | Protected resources requiring login |
| IsAdminUser | Admin only | Admin only | Admin panels, system configuration |
| IsAuthenticatedOrReadOnly | Read: Anyone Write: Authenticated | Read: Anyone Write: Authenticated | Public content with user contributions |
| DjangoModelPermissions | Django model permissions | Per-model permissions | Admin interfaces with granular control |
Custom Permission Classes
Custom permissions implement application-specific authorization rules checking object ownership, user groups, subscription tiers, or complex business logic beyond built-in permission classes. Permission classes inherit from BasePermission implementing has_permission for view-level checks and has_object_permission for object-level verification. View-level permissions execute before retrieving objects checking endpoint access while object-level permissions verify access to specific instances after retrieval. Custom permissions return True to grant access, False to deny silently, or raise PermissionDenied with custom messages. These permissions integrate seamlessly with DRF views and ViewSets enabling flexible authorization patterns.
# Custom permission classes
from rest_framework import permissions
# 1. Object ownership permission
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission: only owner can edit
"""
def has_object_permission(self, request, view, obj):
# Read permissions for everyone (GET, HEAD, OPTIONS)
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions only for owner
return obj.author == request.user
# Usage in ViewSet
from rest_framework import viewsets
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [permissions.IsAuthenticated, IsOwnerOrReadOnly]
# 2. Group-based permission
class IsInGroup(permissions.BasePermission):
"""
Check if user belongs to specific group
"""
def has_permission(self, request, view):
# Get required group from view
required_group = getattr(view, 'required_group', None)
if not required_group:
return False
return request.user.groups.filter(name=required_group).exists()
class AdminArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsInGroup]
required_group = 'Editors'
# 3. Subscription-based permission
class HasActiveSubscription(permissions.BasePermission):
"""
Check if user has active subscription
"""
message = 'You need an active subscription to access this resource.'
def has_permission(self, request, view):
if not request.user.is_authenticated:
return False
# Check user's subscription status
return hasattr(request.user, 'subscription') and \
request.user.subscription.is_active
class PremiumArticleViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Article.objects.filter(is_premium=True)
serializer_class = ArticleSerializer
permission_classes = [HasActiveSubscription]
# 4. Method-specific permission
class IsOwnerOrReadOnlyOrCreate(permissions.BasePermission):
"""
Allow:
- Anyone to read
- Authenticated to create
- Owners to update/delete
"""
def has_permission(self, request, view):
# Read permissions for anyone
if request.method in permissions.SAFE_METHODS:
return True
# Create requires authentication
if request.method == 'POST':
return request.user.is_authenticated
# Update/delete handled by object permission
return True
def has_object_permission(self, request, view, obj):
# Read for anyone
if request.method in permissions.SAFE_METHODS:
return True
# Update/delete only for owner
return obj.author == request.user
# 5. IP-based permission
class IsFromAllowedIP(permissions.BasePermission):
"""
Check if request comes from allowed IP
"""
def has_permission(self, request, view):
allowed_ips = getattr(view, 'allowed_ips', [])
remote_addr = request.META.get('REMOTE_ADDR')
return remote_addr in allowed_ips
class InternalAPIView(APIView):
permission_classes = [IsFromAllowedIP]
allowed_ips = ['192.168.1.100', '10.0.0.50']
# 6. Time-based permission
from datetime import time
class IsBusinessHours(permissions.BasePermission):
"""
Only allow access during business hours
"""
message = 'API access only allowed during business hours (9 AM - 5 PM).'
def has_permission(self, request, view):
from datetime import datetime
current_time = datetime.now().time()
return time(9, 0) <= current_time <= time(17, 0)
# 7. Combining multiple conditions
class IsOwnerOrAdmin(permissions.BasePermission):
"""
Allow access if user is owner OR admin
"""
def has_object_permission(self, request, view, obj):
# Admin always has access
if request.user.is_staff:
return True
# Owner has access
return obj.author == request.user
# 8. Permission with custom error message
class CanPublishArticle(permissions.BasePermission):
"""
Check if user can publish articles
"""
def has_object_permission(self, request, view, obj):
if request.user.is_staff:
return True
# Check if user has publish permission
if not request.user.has_perm('blog.can_publish'):
self.message = 'You do not have permission to publish articles.'
return False
return TrueCombining and Composing Permissions
Multiple permission classes combine with AND logic requiring all permissions to pass for access granting enabling layered authorization checks. Complex permission requirements often need OR logic allowing access if any condition meets requirements implemented through custom permission classes. Per-action permissions in ViewSets enable different authorization rules for list, create, retrieve, update, and destroy operations providing fine-grained control. This flexibility supports sophisticated authorization schemes from simple authentication requirements to complex rules involving ownership, roles, subscriptions, and custom business logic integrated with user models and profiles.
# Combining permissions
from rest_framework import permissions, viewsets
# 1. AND logic (default) - All must pass
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [
permissions.IsAuthenticated, # AND
IsOwnerOrReadOnly, # AND
HasActiveSubscription, # All must pass
]
# 2. OR logic - Custom permission class
class IsOwnerOrAdmin(permissions.BasePermission):
"""
Allow if user is owner OR admin
"""
def has_object_permission(self, request, view, obj):
# OR logic: either condition grants access
return obj.author == request.user or request.user.is_staff
# 3. Per-action permissions in ViewSet
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def get_permissions(self):
"""
Different permissions per action
"""
if self.action == 'list':
# Anyone can list
permission_classes = [permissions.AllowAny]
elif self.action == 'create':
# Authenticated can create
permission_classes = [permissions.IsAuthenticated]
elif self.action == 'retrieve':
# Anyone can view details
permission_classes = [permissions.AllowAny]
elif self.action in ['update', 'partial_update', 'destroy']:
# Only owner or admin can modify
permission_classes = [permissions.IsAuthenticated, IsOwnerOrAdmin]
else:
# Default
permission_classes = [permissions.IsAuthenticated]
return [permission() for permission in permission_classes]
# 4. Custom action permissions
from rest_framework.decorators import action
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
@action(
detail=True,
methods=['post'],
permission_classes=[permissions.IsAdminUser] # Override
)
def publish(self, request, pk=None):
"""Only admins can publish"""
article = self.get_object()
article.published = True
article.save()
return Response({'status': 'published'})
@action(
detail=True,
methods=['post'],
permission_classes=[permissions.IsAuthenticated, IsOwnerOrAdmin]
)
def archive(self, request, pk=None):
"""Owner or admin can archive"""
article = self.get_object()
article.archived = True
article.save()
return Response({'status': 'archived'})
# 5. Complex permission composition
class IsOwnerAndActiveSubscription(permissions.BasePermission):
"""
Require BOTH owner AND active subscription
"""
def has_object_permission(self, request, view, obj):
is_owner = obj.author == request.user
has_subscription = hasattr(request.user, 'subscription') and \
request.user.subscription.is_active
return is_owner and has_subscription
class CanEditPremiumContent(permissions.BasePermission):
"""
Complex condition: Owner with subscription OR Admin
"""
def has_object_permission(self, request, view, obj):
# Admin always allowed
if request.user.is_staff:
return True
# Owner with active subscription
is_owner = obj.author == request.user
has_subscription = hasattr(request.user, 'subscription') and \
request.user.subscription.is_active
return is_owner and has_subscriptionThrottling and Rate Limiting
Throttling limits API request rates preventing abuse, denial-of-service attacks, and resource exhaustion ensuring fair usage and system stability. DRF provides built-in throttle classes including AnonRateThrottle limiting anonymous users, UserRateThrottle limiting authenticated users, and ScopedRateThrottle applying different rates to different endpoints. Throttle rates configure using formats like '100/hour', '1000/day', or '10/minute' with DRF tracking request counts per user or IP address. Exceeded throttle limits return 429 Too Many Requests status codes with Retry-After headers indicating when clients can retry requests. Throttling complements caching strategies protecting APIs from overload while maintaining performance under normal usage patterns.
# Throttling configuration
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day', # Anonymous users: 100 requests per day
'user': '1000/day', # Authenticated users: 1000 requests per day
},
}
# Per-view throttling
from rest_framework.decorators import api_view, throttle_classes
from rest_framework.throttling import UserRateThrottle
class OncePerMinuteThrottle(UserRateThrottle):
rate = '1/minute'
@api_view(['POST'])
@throttle_classes([OncePerMinuteThrottle])
def expensive_operation(request):
"""Limited to 1 request per minute"""
return Response({'status': 'processing'})
# ViewSet throttling
from rest_framework import viewsets
from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
throttle_classes = [AnonRateThrottle, UserRateThrottle]
# Scoped throttling - Different rates per endpoint
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.ScopedRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'contacts': '10/minute', # Contact form submissions
'uploads': '20/hour', # File uploads
'premium': '10000/day', # Premium API access
},
}
from rest_framework.throttling import ScopedRateThrottle
class ContactView(APIView):
throttle_classes = [ScopedRateThrottle]
throttle_scope = 'contacts' # Uses 10/minute rate
def post(self, request):
return Response({'status': 'sent'})
class UploadView(APIView):
throttle_classes = [ScopedRateThrottle]
throttle_scope = 'uploads' # Uses 20/hour rate
def post(self, request):
return Response({'status': 'uploaded'})
# Custom throttle class
from rest_framework.throttling import SimpleRateThrottle
class BurstRateThrottle(SimpleRateThrottle):
"""
Allow burst of 10 requests per minute
"""
scope = 'burst'
def get_cache_key(self, request, view):
if request.user.is_authenticated:
ident = request.user.pk
else:
ident = self.get_ident(request)
return self.cache_format % {
'scope': self.scope,
'ident': ident
}
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'burst': '10/minute',
},
}
# IP-based throttling
class IPRateThrottle(SimpleRateThrottle):
"""
Throttle by IP address
"""
scope = 'ip'
def get_cache_key(self, request, view):
return self.cache_format % {
'scope': self.scope,
'ident': self.get_ident(request) # IP address
}
# Per-action throttling in ViewSet
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def get_throttles(self):
"""
Different throttles per action
"""
if self.action == 'create':
throttle_classes = [OncePerMinuteThrottle]
elif self.action == 'list':
throttle_classes = [UserRateThrottle]
else:
throttle_classes = []
return [throttle() for throttle in throttle_classes]
# Custom throttle with Redis
from django.core.cache import cache
import time
class RedisRateThrottle(SimpleRateThrottle):
"""
High-performance throttling with Redis
"""
cache = cache # Use Django's cache (configure Redis)
scope = 'redis_throttle'
def allow_request(self, request, view):
if self.rate is None:
return True
key = self.get_cache_key(request, view)
if key is None:
return True
# Get current request count
current = cache.get(key, 0)
if current >= self.num_requests:
return False
# Increment counter
cache.set(key, current + 1, self.duration)
return True| Rate Format | Meaning | Use Case | Example |
|---|---|---|---|
| X/second | X requests per second | High-frequency APIs | '10/second' |
| X/minute | X requests per minute | Form submissions, searches | '60/minute' |
| X/hour | X requests per hour | Resource-intensive operations | '100/hour' |
| X/day | X requests per day | Daily quotas, free tiers | '1000/day' |
Access Control Best Practices
- Default to secure permissions: Use IsAuthenticated as default requiring explicit AllowAny for public endpoints preventing accidental exposure
- Implement object-level permissions: Always check object ownership or relationships beyond view-level authorization protecting individual resources
- Use per-action permissions: Override get_permissions in ViewSets applying different rules for list, create, update, and delete operations
- Combine authentication and permissions: Verify identity through authentication then enforce authorization with permissions creating layered security
- Configure appropriate throttle rates: Balance user experience against resource protection setting generous rates for authenticated users, restrictive for anonymous
- Use scoped throttling: Apply different rate limits per endpoint type protecting expensive operations more aggressively than simple queries
- Monitor throttle violations: Log rate limit exceeded events identifying abuse patterns and adjusting limits based on usage data
- Return meaningful error messages: Provide clear feedback when permissions denied helping legitimate users understand access requirements
- Test permission combinations: Verify authorization works correctly testing all permission classes, user types, and edge cases
- Document permission requirements: Clearly document which endpoints require authentication, specific permissions, or special authorization rules
Conclusion
Django REST Framework permissions and throttling provide comprehensive access control protecting APIs from unauthorized access and abuse through authorization rules and rate limiting. Built-in permission classes including AllowAny for public endpoints, IsAuthenticated requiring authentication, IsAdminUser restricting to administrators, and IsAuthenticatedOrReadOnly allowing public reads with authenticated writes handle common authorization patterns. Custom permissions implement application-specific rules through BasePermission subclasses defining has_permission for view-level checks and has_object_permission for object-level verification based on ownership, groups, subscriptions, or complex business logic. Multiple permissions combine with AND logic requiring all checks to pass while custom classes implement OR logic allowing access if any condition succeeds. Per-action permissions in ViewSets enable different authorization for list, create, retrieve, update, and destroy operations with get_permissions method returning appropriate permission classes per action. Throttling limits request rates preventing API abuse through AnonRateThrottle limiting anonymous users, UserRateThrottle limiting authenticated users, and ScopedRateThrottle applying different rates per endpoint with configurable formats like '100/hour' or '1000/day'. Custom throttle classes extend SimpleRateThrottle implementing specific rate limiting logic with Redis caching for high-performance distributed throttling across multiple servers. Permission evaluation occurs after authentication in request processing pipeline with permissions returning True to grant access or False to deny raising PermissionDenied exceptions with custom messages. Best practices include defaulting to secure permissions requiring explicit public access, implementing object-level permissions beyond view-level checks, using per-action permissions for operation-specific rules, combining authentication with permissions for layered security, configuring appropriate throttle rates balancing experience against protection, using scoped throttling for endpoint-specific limits, monitoring throttle violations identifying abuse patterns, returning meaningful error messages helping legitimate users, testing permission combinations verifying correct authorization, and documenting permission requirements clearly communicating access rules. Mastering permissions and throttling enables building secure scalable APIs maintaining performance under load while enforcing business rules integrated with ViewSets, serializers, and core DRF concepts protecting user data throughout API development from initial implementation through production deployment serving thousands of users.
$ share --platform
$ cat /comments/ (0)
$ cat /comments/
// No comments found. Be the first!


