Scope: API security standards for Django REST Framework APIs. Covers default permissions, object-level authorization, serializer mass assignment, throttling, pagination caps, and sensitive data filtering.
Never leave DEFAULT_PERMISSION_CLASSES unconfigured or set to AllowAny globally.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated', # ✅ Secure by default
],
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '1000/day',
'sensitive_action': '5/minute',
},
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
}# VULNERABLE: Any authenticated user can view/edit any invoice by ID
class InvoiceViewSet(viewsets.ModelViewSet):
queryset = Invoice.objects.all() # ❌ Exposes all tenant records
serializer_class = InvoiceSerializer# SAFE: Scope queryset strictly to current authenticated user/organization
class InvoiceViewSet(viewsets.ModelViewSet):
serializer_class = InvoiceSerializer
permission_classes = [permissions.IsAuthenticated, IsInvoiceOwner]
def get_queryset(self):
return Invoice.objects.filter(owner=self.request.user)
# Custom Object Permission
class IsInvoiceOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj.owner == request.userWhen your application offloads data access to a domain service layer:
# SAFE: Ensure service layer explicitly accepts and filters by authenticated user
class InvoiceViewSet(viewsets.ViewSet):
def retrieve(self, request, pk=None):
# Pass request.user context into service layer
invoice = InvoiceService.get_for_user(invoice_id=pk, user=request.user)
if not invoice:
raise NotFound("Invoice not found.")
return Response(InvoiceSerializer(invoice).data)Never allow client updates to sensitive fields like role, is_verified, is_billing_admin, balance, or tenant_id.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__' # ❌ Allows client to set is_staff, is_superuser, groupsclass UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email', 'bio', 'is_staff', 'is_billing_admin']
read_only_fields = ['id', 'is_staff', 'is_billing_admin'] # ✅ Client cannot modify privilege fields- Apply dedicated scoped throttles to sensitive endpoints (login, password reset, OTP verification, payment initiation).
- Important Design Note: Throttling is one layer in defense-in-depth and does not replace authentication, CAPTCHA, or anomaly detection.
class PasswordResetView(APIView):
throttle_classes = [ScopedRateThrottle]
throttle_scope = 'sensitive_action'
# ...Always configure a hard ceiling (max_page_size) on paginated endpoints to prevent attackers from executing denial-of-service queries like ?page_size=1000000.
class SafePagination(PageNumberPagination):
page_size = 20
page_size_query_param = 'page_size'
max_page_size = 100 # ✅ Enforce hard upper bound-
DEFAULT_PERMISSION_CLASSESdefaults toIsAuthenticated. - ViewSets override
get_queryset()or passrequest.userinto domain service layer lookups. - Serializers use explicit
read_only_fieldsfor all privilege, tier, and financial fields. - Sensitive actions have dedicated
ScopedRateThrottleapplied. - Pagination enforces
max_page_sizelimits.