0
# Authentication & Permissions
1
2
Django REST Framework provides a comprehensive authentication and permissions system with full type safety. The type stubs enable precise type checking for security configurations, custom authentication backends, and permission logic.
3
4
## Authentication Classes
5
6
### BaseAuthentication
7
8
```python { .api }
9
class BaseAuthentication:
10
"""Base class for all authentication backends."""
11
12
def authenticate(self, request: Request) -> tuple[Any, Any] | None:
13
"""
14
Authenticate the request and return a two-tuple of (user, token).
15
16
Returns:
17
tuple[Any, Any] | None: (user, auth) or None if not authenticated
18
"""
19
...
20
21
def authenticate_header(self, request: Request) -> str | None:
22
"""
23
Return a string to be used as the value of the WWW-Authenticate
24
header in a 401 Unauthenticated response.
25
26
Returns:
27
str | None: Authentication header value or None
28
"""
29
...
30
```
31
32
### SessionAuthentication
33
34
```python { .api }
35
class SessionAuthentication(BaseAuthentication):
36
"""Django session-based authentication."""
37
38
def authenticate(self, request: Request) -> tuple[Any, Any] | None: ...
39
def authenticate_header(self, request: Request) -> str | None: ...
40
def enforce_csrf(self, request: Request) -> None: ...
41
```
42
43
### BasicAuthentication
44
45
```python { .api }
46
class BasicAuthentication(BaseAuthentication):
47
"""HTTP Basic authentication against username/password."""
48
49
www_authenticate_realm: str
50
51
def authenticate(self, request: Request) -> tuple[Any, Any] | None: ...
52
def authenticate_header(self, request: Request) -> str: ...
53
def authenticate_credentials(
54
self,
55
userid: str,
56
password: str,
57
request: Request | None = None
58
) -> tuple[Any, None]: ...
59
```
60
61
**Parameters:**
62
- `www_authenticate_realm: str` - Realm for WWW-Authenticate header
63
- `userid: str` - Username from Basic auth header
64
- `password: str` - Password from Basic auth header
65
- `request: Request | None` - Current request object
66
67
### TokenAuthentication
68
69
```python { .api }
70
class TokenAuthentication(BaseAuthentication):
71
"""Token-based authentication using DRF tokens."""
72
73
keyword: str # Default: 'Token'
74
model: type[Model] | None
75
76
def authenticate(self, request: Request) -> tuple[Any, Any] | None: ...
77
def authenticate_header(self, request: Request) -> str: ...
78
def authenticate_credentials(self, key: str) -> tuple[Any, Any]: ...
79
def get_model(self) -> type[Model]: ...
80
```
81
82
**Parameters:**
83
- `keyword: str` - Token keyword in Authorization header (default: 'Token')
84
- `model: type[Model] | None` - Token model class
85
- `key: str` - Token key from Authorization header
86
87
### RemoteUserAuthentication
88
89
```python { .api }
90
class RemoteUserAuthentication(BaseAuthentication):
91
"""Authentication for users authenticated by external systems."""
92
93
header: str # Default: 'REMOTE_USER'
94
95
def authenticate(self, request: Request) -> tuple[Any, Any] | None: ...
96
def clean_username(self, username: str) -> str: ...
97
```
98
99
**Parameters:**
100
- `header: str` - HTTP header containing remote username
101
102
## Authentication Utilities
103
104
### Helper Functions
105
106
```python { .api }
107
def get_authorization_header(request: Request) -> bytes:
108
"""
109
Extract the authorization header from the request.
110
111
Args:
112
request: The DRF Request object
113
114
Returns:
115
bytes: Authorization header value
116
"""
117
...
118
```
119
120
### CSRF Protection
121
122
```python { .api }
123
class CSRFCheck(CsrfViewMiddleware):
124
"""CSRF protection middleware for DRF."""
125
126
def reject(self, request: HttpRequest, reason: str) -> None: ...
127
def process_view(
128
self,
129
request: HttpRequest,
130
callback: Callable,
131
callback_args: tuple,
132
callback_kwargs: dict[str, Any]
133
) -> HttpResponse | None: ...
134
```
135
136
## Permission Classes
137
138
### BasePermission
139
140
```python { .api }
141
class BasePermission:
142
"""Base class for all permission checks."""
143
144
def has_permission(self, request: Request, view: APIView) -> bool:
145
"""
146
Return True if permission is granted for the view.
147
148
Args:
149
request: The DRF request object
150
view: The view being accessed
151
152
Returns:
153
bool: True if permission granted
154
"""
155
...
156
157
def has_object_permission(
158
self,
159
request: Request,
160
view: APIView,
161
obj: Any
162
) -> bool:
163
"""
164
Return True if permission is granted for the object.
165
166
Args:
167
request: The DRF request object
168
view: The view being accessed
169
obj: The object being accessed
170
171
Returns:
172
bool: True if permission granted
173
"""
174
...
175
```
176
177
### Built-in Permission Classes
178
179
```python { .api }
180
class AllowAny(BasePermission):
181
"""Allow unrestricted access, regardless of authentication."""
182
pass
183
184
class IsAuthenticated(BasePermission):
185
"""Allow access only to authenticated users."""
186
pass
187
188
class IsAdminUser(BasePermission):
189
"""Allow access only to admin users."""
190
pass
191
192
class IsAuthenticatedOrReadOnly(BasePermission):
193
"""Allow read-only access to any user, write access to authenticated users."""
194
pass
195
```
196
197
### Django Model Permissions
198
199
```python { .api }
200
class DjangoModelPermissions(BasePermission):
201
"""Permission class that ties into Django's model permissions system."""
202
203
authenticated_users_only: bool
204
perms_map: dict[str, list[str]]
205
206
def get_required_permissions(
207
self,
208
method: str,
209
model_cls: type[Model]
210
) -> list[str]: ...
211
def get_required_object_permissions(
212
self,
213
method: str,
214
model_cls: type[Model]
215
) -> list[str]: ...
216
217
class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions):
218
"""Django model permissions with anonymous read-only access."""
219
220
authenticated_users_only: bool
221
222
class DjangoObjectPermissions(DjangoModelPermissions):
223
"""Django object-level permissions."""
224
pass
225
```
226
227
**Parameters:**
228
- `authenticated_users_only: bool` - Require authentication for all access
229
- `perms_map: dict[str, list[str]]` - Mapping of HTTP methods to required permissions
230
231
## Permission Operators
232
233
### Logical Operators
234
235
```python { .api }
236
class AND:
237
"""Logical AND operator for permissions."""
238
239
def __init__(self, op1: BasePermission, op2: BasePermission) -> None: ...
240
def has_permission(self, request: Request, view: APIView) -> bool: ...
241
def has_object_permission(self, request: Request, view: APIView, obj: Any) -> bool: ...
242
243
class OR:
244
"""Logical OR operator for permissions."""
245
246
def __init__(self, op1: BasePermission, op2: BasePermission) -> None: ...
247
def has_permission(self, request: Request, view: APIView) -> bool: ...
248
def has_object_permission(self, request: Request, view: APIView, obj: Any) -> bool: ...
249
250
class NOT:
251
"""Logical NOT operator for permissions."""
252
253
def __init__(self, op1: BasePermission) -> None: ...
254
def has_permission(self, request: Request, view: APIView) -> bool: ...
255
def has_object_permission(self, request: Request, view: APIView, obj: Any) -> bool: ...
256
```
257
258
### Permission Composition
259
260
```python { .api }
261
# Combine permissions with operators
262
permission_classes = [IsAuthenticated & (IsOwner | IsAdmin)]
263
permission_classes = [IsAuthenticated & ~IsBlocked]
264
permission_classes = [(IsAuthenticated & IsOwner) | IsAdmin]
265
```
266
267
## Usage Examples
268
269
### Custom Authentication Backend
270
271
```python { .api }
272
from rest_framework.authentication import BaseAuthentication
273
from rest_framework.exceptions import AuthenticationFailed
274
from django.contrib.auth.models import User
275
276
class APIKeyAuthentication(BaseAuthentication):
277
"""Custom API key authentication."""
278
279
def authenticate(self, request: Request) -> tuple[User, str] | None:
280
api_key = request.META.get('HTTP_X_API_KEY')
281
if not api_key:
282
return None
283
284
try:
285
# Look up user by API key
286
profile = UserProfile.objects.select_related('user').get(api_key=api_key)
287
return (profile.user, api_key)
288
except UserProfile.DoesNotExist:
289
raise AuthenticationFailed('Invalid API key')
290
291
def authenticate_header(self, request: Request) -> str:
292
return 'X-API-Key'
293
294
class JWTAuthentication(BaseAuthentication):
295
"""JWT token authentication."""
296
297
def authenticate(self, request: Request) -> tuple[User, dict[str, Any]] | None:
298
auth_header = request.META.get('HTTP_AUTHORIZATION')
299
if not auth_header or not auth_header.startswith('Bearer '):
300
return None
301
302
token = auth_header[7:] # Remove 'Bearer ' prefix
303
304
try:
305
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
306
user = User.objects.get(id=payload['user_id'])
307
return (user, payload)
308
except (jwt.DecodeError, jwt.ExpiredSignatureError, User.DoesNotExist):
309
raise AuthenticationFailed('Invalid token')
310
311
def authenticate_header(self, request: Request) -> str:
312
return 'Bearer'
313
```
314
315
### Custom Permission Classes
316
317
```python { .api }
318
from rest_framework.permissions import BasePermission
319
320
class IsOwnerOrReadOnly(BasePermission):
321
"""Allow owners to edit, others to read only."""
322
323
def has_object_permission(
324
self,
325
request: Request,
326
view: APIView,
327
obj: Any
328
) -> bool:
329
# Read permissions for any request
330
if request.method in ['GET', 'HEAD', 'OPTIONS']:
331
return True
332
333
# Write permissions only to owner
334
return obj.owner == request.user
335
336
class IsSuperUserOrOwner(BasePermission):
337
"""Allow superusers or object owners full access."""
338
339
def has_permission(self, request: Request, view: APIView) -> bool:
340
return request.user and request.user.is_authenticated
341
342
def has_object_permission(
343
self,
344
request: Request,
345
view: APIView,
346
obj: Any
347
) -> bool:
348
return (
349
request.user.is_superuser or
350
getattr(obj, 'user', None) == request.user or
351
getattr(obj, 'owner', None) == request.user
352
)
353
354
class IsInGroupOrReadOnly(BasePermission):
355
"""Allow group members to edit, others to read only."""
356
357
def __init__(self, group_name: str) -> None:
358
self.group_name = group_name
359
360
def has_permission(self, request: Request, view: APIView) -> bool:
361
if request.method in ['GET', 'HEAD', 'OPTIONS']:
362
return True
363
364
return (
365
request.user.is_authenticated and
366
request.user.groups.filter(name=self.group_name).exists()
367
)
368
369
class HasModelPermission(BasePermission):
370
"""Check Django model permissions."""
371
372
def __init__(self, model: type[Model], action: str) -> None:
373
self.model = model
374
self.action = action
375
376
def has_permission(self, request: Request, view: APIView) -> bool:
377
if not request.user.is_authenticated:
378
return False
379
380
permission_name = f"{self.model._meta.app_label}.{self.action}_{self.model._meta.model_name}"
381
return request.user.has_perm(permission_name)
382
```
383
384
### View-Level Authentication Configuration
385
386
```python { .api }
387
from rest_framework.views import APIView
388
from rest_framework.authentication import (
389
SessionAuthentication,
390
TokenAuthentication,
391
BasicAuthentication
392
)
393
from rest_framework.permissions import IsAuthenticated, IsAdminUser
394
395
class MultiAuthView(APIView):
396
"""View with multiple authentication methods."""
397
398
authentication_classes = [
399
SessionAuthentication,
400
TokenAuthentication,
401
BasicAuthentication
402
]
403
permission_classes = [IsAuthenticated]
404
405
def get(self, request: Request) -> Response:
406
# User is guaranteed to be authenticated
407
return Response({
408
'user': request.user.username,
409
'auth_method': type(request.auth).__name__
410
})
411
412
class AdminOnlyView(APIView):
413
"""View restricted to admin users."""
414
415
authentication_classes = [SessionAuthentication, TokenAuthentication]
416
permission_classes = [IsAuthenticated, IsAdminUser]
417
418
def get(self, request: Request) -> Response:
419
# User is guaranteed to be authenticated admin
420
return Response({'message': 'Admin access granted'})
421
422
class DynamicPermissionView(APIView):
423
"""View with dynamic permission checking."""
424
425
def get_permissions(self) -> list[BasePermission]:
426
if self.request.method == 'GET':
427
permission_classes = [AllowAny]
428
elif self.request.method == 'POST':
429
permission_classes = [IsAuthenticated]
430
else:
431
permission_classes = [IsAuthenticated, IsAdminUser]
432
433
return [permission() for permission in permission_classes]
434
```
435
436
### ViewSet Authentication & Permissions
437
438
```python { .api }
439
from rest_framework import viewsets
440
from rest_framework.decorators import action
441
442
class BookViewSet(viewsets.ModelViewSet[Book]):
443
"""ViewSet with action-specific permissions."""
444
445
queryset = Book.objects.all()
446
serializer_class = BookSerializer
447
448
def get_permissions(self) -> list[BasePermission]:
449
"""Different permissions for different actions."""
450
if self.action == 'list':
451
permission_classes = [AllowAny]
452
elif self.action in ['create', 'update', 'partial_update']:
453
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
454
elif self.action == 'destroy':
455
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly, IsAdminUser]
456
else:
457
permission_classes = [IsAuthenticated]
458
459
return [permission() for permission in permission_classes]
460
461
def get_authenticators(self) -> list[BaseAuthentication]:
462
"""Different authentication for different actions."""
463
if self.action in ['list', 'retrieve']:
464
# Public actions allow multiple auth methods
465
return [
466
SessionAuthentication(),
467
TokenAuthentication(),
468
BasicAuthentication()
469
]
470
else:
471
# Sensitive actions require token or session auth only
472
return [
473
SessionAuthentication(),
474
TokenAuthentication()
475
]
476
477
@action(detail=False, methods=['get'], permission_classes=[IsAuthenticated])
478
def my_books(self, request: Request) -> Response:
479
"""Get current user's books."""
480
books = Book.objects.filter(owner=request.user)
481
serializer = self.get_serializer(books, many=True)
482
return Response(serializer.data)
483
484
@action(detail=True, methods=['post'], permission_classes=[IsAuthenticated, IsOwnerOrReadOnly])
485
def publish(self, request: Request, pk: str | None = None) -> Response:
486
"""Publish a book."""
487
book = self.get_object()
488
book.is_published = True
489
book.published_date = timezone.now()
490
book.save()
491
return Response({'status': 'published'})
492
```
493
494
### Permission Decorators
495
496
```python { .api }
497
from rest_framework.decorators import (
498
api_view,
499
authentication_classes,
500
permission_classes
501
)
502
503
@api_view(['GET'])
504
@permission_classes([AllowAny])
505
def public_endpoint(request: Request) -> Response:
506
"""Publicly accessible endpoint."""
507
return Response({'message': 'Public data'})
508
509
@api_view(['POST'])
510
@authentication_classes([TokenAuthentication])
511
@permission_classes([IsAuthenticated])
512
def protected_endpoint(request: Request) -> Response:
513
"""Protected endpoint requiring token auth."""
514
return Response({
515
'message': f'Hello {request.user.username}',
516
'user_id': request.user.id
517
})
518
519
@api_view(['GET', 'POST'])
520
@authentication_classes([SessionAuthentication, TokenAuthentication])
521
@permission_classes([IsAuthenticated])
522
def user_profile(request: Request) -> Response:
523
"""User profile endpoint with multiple auth methods."""
524
if request.method == 'GET':
525
serializer = UserSerializer(request.user)
526
return Response(serializer.data)
527
elif request.method == 'POST':
528
serializer = UserSerializer(request.user, data=request.data, partial=True)
529
if serializer.is_valid():
530
serializer.save()
531
return Response(serializer.data)
532
return Response(serializer.errors, status=400)
533
```
534
535
## Token Authentication Setup
536
537
### Token Model
538
539
```python { .api }
540
from rest_framework.authtoken.models import Token
541
542
# Token model fields
543
class Token(models.Model):
544
key: models.CharField
545
user: models.OneToOneField
546
created: models.DateTimeField
547
548
@classmethod
549
def generate_key(cls) -> str: ...
550
551
class TokenProxy(Token):
552
"""Proxy model for Token."""
553
554
class Meta:
555
proxy = True
556
```
557
558
### Token Views
559
560
```python { .api }
561
from rest_framework.authtoken.views import ObtainAuthToken
562
from rest_framework.authtoken import views
563
564
class ObtainAuthToken(APIView):
565
"""View for obtaining authentication tokens."""
566
567
serializer_class: type[Serializer]
568
569
def post(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
570
def get_serializer_context(self) -> dict[str, Any]: ...
571
def get_serializer(self, *args: Any, **kwargs: Any) -> Serializer: ...
572
573
# Pre-configured view instance
574
obtain_auth_token: Callable[..., Response]
575
```
576
577
### Token Serializer
578
579
```python { .api }
580
from rest_framework.authtoken.serializers import AuthTokenSerializer
581
582
class AuthTokenSerializer(serializers.Serializer):
583
"""Serializer for token authentication."""
584
585
username: serializers.CharField
586
password: serializers.CharField
587
token: serializers.CharField
588
589
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: ...
590
```
591
592
## Security Best Practices
593
594
### Secure Authentication Configuration
595
596
```python { .api }
597
# Production-ready authentication setup
598
class SecureAPIView(APIView):
599
"""Secure API view configuration."""
600
601
authentication_classes = [
602
TokenAuthentication, # Primary auth method
603
SessionAuthentication # Fallback for web UI
604
]
605
permission_classes = [IsAuthenticated]
606
607
def dispatch(self, request: Request, *args: Any, **kwargs: Any) -> Response:
608
# Add security headers
609
response = super().dispatch(request, *args, **kwargs)
610
response['X-Content-Type-Options'] = 'nosniff'
611
response['X-Frame-Options'] = 'DENY'
612
response['X-XSS-Protection'] = '1; mode=block'
613
return response
614
```
615
616
### Rate Limiting with Permissions
617
618
```python { .api }
619
from rest_framework.throttling import UserRateThrottle
620
621
class RateLimitedPermission(BasePermission):
622
"""Permission that includes rate limiting."""
623
624
def has_permission(self, request: Request, view: APIView) -> bool:
625
# Check base permission
626
if not request.user.is_authenticated:
627
return False
628
629
# Apply rate limiting
630
throttle = UserRateThrottle()
631
if not throttle.allow_request(request, view):
632
return False
633
634
return True
635
```
636
637
This comprehensive authentication and permissions system provides type-safe security controls with full mypy support, enabling confident implementation of authentication backends, permission logic, and access control patterns.