Django: Authentication and Permissions in Django REST Framework (DRF)
Authentication and permissions in Django REST Framework (DRF) are critical for securing RESTful APIs, ensuring that only authorized users can access or modify resources. Integrated with Django’s Model-View-Template (MVT) architecture, DRF provides robust mechanisms for user authentication (verifying identity) and permissions (controlling access). This tutorial explores authentication and permissions in DRF, covering setup, configuration, and practical applications for securing APIs in applications like blogs, e-commerce platforms, or dashboards.
01. Why Authentication and Permissions in DRF?
Authentication identifies who a user is (e.g., via tokens, sessions), while permissions determine what they can do (e.g., read, write). DRF’s flexible authentication and permission systems protect sensitive data and restrict actions, making them essential for secure API development. For example, a blog API might allow anyone to read posts but restrict post creation to authenticated authors.
Example: Basic DRF Authentication Setup
# Install DRF and JWT
pip install djangorestframework djangorestframework-simplejwt
# myproject/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_simplejwt', # JWT authentication
'myapp',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
# myproject/urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('myapp.urls')),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
Output:
JWT authentication configured; token endpoints at http://127.0.0.1:8000/api/token/.
Explanation:
rest_framework_simplejwt
- Enables JSON Web Token (JWT) authentication.DEFAULT_AUTHENTICATION_CLASSES
- Sets JWT as the default authentication method.DEFAULT_PERMISSION_CLASSES
- Restricts access to authenticated users by default.
02. Key Authentication and Permission Concepts
DRF provides a variety of authentication and permission classes to secure APIs. The table below summarizes key concepts and their roles:
Concept | Description | Use Case |
---|---|---|
Authentication | Verifies user identity | Token-based access, session login |
Permission | Controls access to resources | Restrict CRUD operations |
JWT Authentication | Uses tokens for stateless authentication | Secure API for mobile/web clients |
Custom Permissions | Define specific access rules | Role-based or object-level access |
2.1 JWT Authentication
Example: JWT-Protected API
# myapp/models.py
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
# myapp/serializers.py
from rest_framework import serializers
from .models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['id', 'title', 'content', 'author', 'created_at']
read_only_fields = ['author', 'created_at']
# myapp/views.py
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from .models import Article
from .serializers import ArticleSerializer
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticated]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
# myapp/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet
router = DefaultRouter()
router.register(r'articles', ArticleViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Output:
API at http://127.0.0.1:8000/api/articles/ requires JWT token for access.
Explanation:
IsAuthenticated
- Ensures only users with valid JWT tokens can access the API.perform_create
- Sets the article’s author to the authenticated user.- Token obtained via
/api/token/
must be included in theAuthorization: Bearer
header.
2.2 Built-in Permission Classes
Example: Read-Only for Unauthenticated Users
# myapp/views.py
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from .models import Article
from .serializers import ArticleSerializer
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
Output:
GET http://127.0.0.1:8000/api/articles/ allowed for all; POST requires JWT token.
Explanation:
IsAuthenticatedOrReadOnly
- Allows unauthenticated users to read (GET) but requires authentication for write actions (POST, PUT, DELETE).- Ideal for public-facing APIs with restricted modifications.
2.3 Custom Permission Classes
Example: Author-Only Edit Permission
# myapp/permissions.py
from rest_framework import permissions
class IsAuthorOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# Allow GET, HEAD, OPTIONS for all
if request.method in permissions.SAFE_METHODS:
return True
# Allow write actions only if user is the author
return obj.author == request.user
# myapp/views.py
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from .models import Article
from .serializers import ArticleSerializer
from .permissions import IsAuthorOrReadOnly
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
Output:
Only article authors can edit/delete at http://127.0.0.1:8000/api/articles/{id}/.
Explanation:
IsAuthorOrReadOnly
- Custom permission checks if the user is the article’s author for write actions.has_object_permission
- Applies per-object access control.- Combined with
IsAuthenticated
to ensure only authenticated users can attempt writes.
2.4 Combining Authentication and Permissions
Example: Secure Task API
# myapp/models.py
from django.db import models
class Task(models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
owner = models.ForeignKey('auth.User', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
# myapp/serializers.py
from rest_framework import serializers
from .models import Task
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = ['id', 'title', 'description', 'owner', 'created_at']
read_only_fields = ['owner', 'created_at']
# myapp/permissions.py
from rest_framework import permissions
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj.owner == request.user
# myapp/views.py
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from .models import Task
from .serializers import TaskSerializer
from .permissions import IsOwner
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
permission_classes = [IsAuthenticated, IsOwner]
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
def get_queryset(self):
# Only return tasks owned by the user
return Task.objects.filter(owner=self.request.user)
# myapp/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import TaskViewSet
router = DefaultRouter()
router.register(r'tasks', TaskViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Output:
Tasks at http://127.0.0.1:8000/api/tasks/ accessible only to their owners with JWT token.
Explanation:
IsAuthenticated
- Requires JWT token.IsOwner
- Ensures users can only access their own tasks.get_queryset
- Filters tasks to the authenticated user.
2.5 Incorrect Authentication/Permission Usage
Example: Unprotected API
# myapp/views.py (Incorrect)
from rest_framework import viewsets
from .models import Article
from .serializers import ArticleSerializer
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# No permission_classes defined
Output:
Anyone can access http://127.0.0.1:8000/api/articles/, risking unauthorized actions.
Explanation:
- Omitting
permission_classes
allows unrestricted access. - Solution: Add
IsAuthenticated
or custom permissions.
03. Effective Usage
3.1 Recommended Practices
- Use JWT authentication for stateless APIs, combine with permission classes, and implement custom permissions for fine-grained control.
Example: Comprehensive Secure API
# myproject/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_simplejwt',
'myapp',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
# myapp/models.py
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
# myapp/serializers.py
from rest_framework import serializers
from .models import Article
class ArticleSerializer(serializers.ModelSerializer):
author_username = serializers.ReadOnlyField(source='author.username')
class Meta:
model = Article
fields = ['id', 'title', 'content', 'author', 'author_username', 'created_at']
read_only_fields = ['author', 'created_at']
# myapp/permissions.py
from rest_framework import permissions
class IsAuthorOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.author == request.user
# myapp/views.py
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from .models import Article
from .serializers import ArticleSerializer
from .permissions import IsAuthorOrReadOnly
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticatedOrReadOnly, IsAuthorOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
# myapp/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet
router = DefaultRouter()
router.register(r'articles', ArticleViewSet)
urlpatterns = [
path('', include(router.urls)),
]
# myproject/urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('myapp.urls')),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
Output:
Secure API at http://127.0.0.1:8000/api/articles/ with JWT and author-only edits.
- JWT ensures stateless authentication.
IsAuthenticatedOrReadOnly
- Allows public reads.IsAuthorOrReadOnly
- Restricts edits to authors.
3.2 Practices to Avoid
- Avoid unprotected endpoints or overly permissive permissions.
Example: Overly Permissive Permission
# myapp/views.py (Incorrect)
from rest_framework import viewsets
from rest_framework.permissions import AllowAny
from .models import Article
from .serializers import ArticleSerializer
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [AllowAny] # Incorrect: No restrictions
Output:
Anyone can modify articles, risking data integrity.
AllowAny
- Removes all access controls.- Solution: Use
IsAuthenticated
or custom permissions.
04. Common Use Cases
4.1 Secure Blog API
Allow public reads but restrict writes to authenticated authors.
Example: Blog API
# myapp/views.py
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from .models import Article
from .serializers import ArticleSerializer
from .permissions import IsAuthorOrReadOnly
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticatedOrReadOnly, IsAuthorOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
Output:
Public reads, author-only writes at http://127.0.0.1:8000/api/articles/.
Explanation:
- Combines permissions for flexible access control.
- JWT ensures secure authentication.
4.2 Task Management API
Restrict task access to their owners with JWT authentication.
Example: Task API
# myapp/views.py
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from .models import Task
from .serializers import TaskSerializer
from .permissions import IsOwner
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
permission_classes = [IsAuthenticated, IsOwner]
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
def get_queryset(self):
return Task.objects.filter(owner=self.request.user)
Output:
User-specific tasks at http://127.0.0.1:8000/api/tasks/ with JWT token.
Explanation:
IsOwner
- Ensures object-level access control.- Queryset filtering enhances security.
Conclusion
DRF’s authentication and permission systems, integrated with Django’s Model-View-Template architecture, provide a secure framework for API development. Key takeaways:
- Use JWT for stateless authentication.
- Apply built-in permissions like
IsAuthenticatedOrReadOnly
. - Create custom permissions for fine-grained control.
- Avoid unprotected endpoints or permissive settings.
With DRF authentication and permissions, you can build secure, role-based APIs for modern applications!
Comments
Post a Comment