Django REST Authentication
Authentication is a critical component of any web application or API. It verifies the identity of users and ensures only authorized individuals can access specific resources. In this tutorial, we'll explore how to implement authentication in Django REST Framework (DRF) to secure your API endpoints.
Introduction to REST Authentication
Authentication for REST APIs differs from traditional web applications. While web apps typically use session-based authentication with cookies, APIs often use token-based or other stateless authentication methods. Django REST Framework provides several built-in authentication schemes that are easy to implement.
Why Authentication Matters for APIs
- Security: Prevents unauthorized access to sensitive data and operations
- User-specific data: Allows your API to return personalized content
- Rate limiting: Helps control API usage per user
- Audit trails: Tracks who performed what actions within your system
Built-in Authentication Classes
Django REST Framework ships with several authentication classes that you can use out of the box:
- BasicAuthentication - Uses HTTP Basic Auth with username and password
- SessionAuthentication - Uses Django's session framework
- TokenAuthentication - Uses a simple token-based scheme
- RemoteUserAuthentication - For delegating authentication to the web server
Let's explore how to implement each of these methods.
Setting Up Your Django Project
Before we dive into authentication, let's make sure we have a basic Django REST Framework project set up.
# Create a new Django project
django-admin startproject auth_demo
cd auth_demo
# Create a new app
python manage.py startapp api
# Install Django REST Framework
pip install djangorestframework
Update your settings.py
file to include DRF:
INSTALLED_APPS = [
# Django apps...
'rest_framework',
'api',
]
Basic Authentication
Basic Authentication is the simplest form of authentication, where the client sends the username and password with each request. It's not secure unless used over HTTPS.
Configuring Basic Authentication
In your settings.py
file:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
]
}
Creating a Simple View
Let's create a simple view that requires authentication:
# api/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
class ProtectedView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
content = {'message': 'Hello, you are authenticated!'}
return Response(content)
Add URL configuration:
# api/urls.py
from django.urls import path
from .views import ProtectedView
urlpatterns = [
path('protected/', ProtectedView.as_view(), name='protected'),
]
Update the project's URL configuration:
# auth_demo/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api.urls')),
]
Testing Basic Authentication
You can test this using curl:
curl -u username:password http://localhost:8000/api/protected/
The response should be:
{"message": "Hello, you are authenticated!"}
If you don't provide credentials or provide incorrect ones, you'll get an HTTP 401 Unauthorized response.
Token Authentication
Basic Authentication isn't ideal for production APIs because it requires sending the username and password with every request. Token Authentication is a better alternative.
Setting Up Token Authentication
First, add rest_framework.authtoken
to your installed apps:
INSTALLED_APPS = [
# ...
'rest_framework',
'rest_framework.authtoken',
'api',
]
Run migrations to create the token table:
python manage.py migrate
Update your settings:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
]
}
Creating Tokens
You need to create a token for each user. You can do this in several ways:
- Using the admin interface
- Using the
manage.py
command - Automatically when a user is created via signals
- Via an API endpoint
Let's implement an API endpoint for token generation:
# api/views.py
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
class CustomAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data,
context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({
'token': token.key,
'user_id': user.pk,
'email': user.email
})
Add a URL for token creation:
# api/urls.py
from django.urls import path
from .views import ProtectedView, CustomAuthToken
urlpatterns = [
path('protected/', ProtectedView.as_view(), name='protected'),
path('token/', CustomAuthToken.as_view(), name='api_token_auth'),
]
Using Token Authentication
To authenticate with a token, include it in the HTTP Authorization header:
curl -X GET http://localhost:8000/api/protected/ -H "Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
The response:
{"message": "Hello, you are authenticated!"}
Session Authentication
Session authentication uses Django's session framework. It's useful for AJAX requests from your web application.
Setting Up Session Authentication
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
]
}
This approach works well with the Django REST Framework browsable API, allowing users to log in through the browser.
JWT Authentication
JSON Web Tokens (JWT) are a popular authentication mechanism for modern web applications and APIs. They're stateless and compact, making them ideal for mobile applications.
Installing Django REST Framework JWT
First, install the necessary package:
pip install djangorestframework-simplejwt
Update your settings:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
}
Add JWT-specific settings:
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': True,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUTH_HEADER_TYPES': ('Bearer',),
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
}
Add URLs for token creation and refresh:
# auth_demo/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('api.urls')),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
Using JWT Authentication
To get a token:
curl -X POST http://localhost:8000/api/token/ -d "username=myuser&password=mypassword"
Response:
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
Use the access token to authenticate:
curl -X GET http://localhost:8000/api/protected/ -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
When the access token expires, use the refresh token to get a new one:
curl -X POST http://localhost:8000/api/token/refresh/ -d "refresh=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
Real-World Authentication Example
Let's build a real-world example of a blog API with authentication. We'll create user registration, login, and protected endpoints.
Creating Models
# api/models.py
from django.db import models
from django.contrib.auth.models import User
class BlogPost(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
Creating Serializers
# api/serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import BlogPost
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
def create(self, validated_data):
user = User.objects.create_user(
username=validated_data['username'],
email=validated_data.get('email', ''),
password=validated_data['password']
)
return user
class BlogPostSerializer(serializers.ModelSerializer):
author = serializers.ReadOnlyField(source='author.username')
class Meta:
model = BlogPost
fields = ('id', 'title', 'content', 'author', 'created_at')
Creating Views
# api/views.py
from rest_framework import generics, permissions
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import BlogPost
from .serializers import UserSerializer, BlogPostSerializer
from django.contrib.auth.models import User
class UserRegistrationView(generics.CreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [permissions.AllowAny]
class UserProfileView(APIView):
permission_classes = [permissions.IsAuthenticated]
def get(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
class BlogPostListView(generics.ListCreateAPIView):
queryset = BlogPost.objects.all()
serializer_class = BlogPostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
class BlogPostDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = BlogPost.objects.all()
serializer_class = BlogPostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_permissions(self):
if self.request.method in ['PUT', 'PATCH', 'DELETE']:
return [permissions.IsAuthenticated(), IsAuthorOrReadOnly()]
return super().get_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
URL Configuration
# api/urls.py
from django.urls import path
from .views import (
UserRegistrationView,
UserProfileView,
BlogPostListView,
BlogPostDetailView,
)
urlpatterns = [
path('register/', UserRegistrationView.as_view(), name='register'),
path('profile/', UserProfileView.as_view(), name='profile'),
path('posts/', BlogPostListView.as_view(), name='post-list'),
path('posts/<int:pk>/', BlogPostDetailView.as_view(), name='post-detail'),
]
Custom Authentication
Sometimes, the built-in authentication classes may not meet your needs. In such cases, you can create custom authentication methods.
Creating a Custom Authentication Class
# api/authentication.py
from rest_framework import authentication
from rest_framework import exceptions
from django.contrib.auth.models import User
import base64
class CustomHeaderAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
auth_header = request.META.get('HTTP_X_API_KEY')
if not auth_header:
return None
try:
# This is just an example - in a real application,
# you would use a more secure approach
decoded = base64.b64decode(auth_header).decode('utf-8')
username, api_key = decoded.split(':')
user = User.objects.get(username=username)
# Verify API key (this would usually check against a stored key)
if user.profile.api_key != api_key:
raise exceptions.AuthenticationFailed('Invalid API key')
except User.DoesNotExist:
raise exceptions.AuthenticationFailed('No such user')
except Exception as e:
raise exceptions.AuthenticationFailed('Authentication failed')
return (user, None) # authentication successful
Add the custom authentication to your project's settings:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'api.authentication.CustomHeaderAuthentication',
]
}
Best Practices for API Authentication
-
Always use HTTPS: This ensures that credentials and tokens aren't transmitted in plain text.
-
Token expiration: Set reasonable expiration times for tokens. For JWT, use shorter expiry times for access tokens (minutes to hours) and longer for refresh tokens (days).
-
Rate limiting: Implement rate limiting to prevent brute force attacks.
-
Multi-factor authentication: For sensitive operations, consider implementing MFA.
-
Proper error messages: Don't reveal too much information in error messages that could help attackers.
-
Token storage: Client-side applications should store tokens securely (e.g., in HTTP-only cookies, not localStorage).
-
Revocation mechanisms: Have a way to revoke tokens if a security breach is detected.
Summary
We've covered multiple authentication methods available in Django REST Framework:
- Basic Authentication (simple but only secure over HTTPS)
- Token Authentication (better for API use cases)
- Session Authentication (works well with browser-based interfaces)
- JWT Authentication (stateless and suitable for scaling)
- Custom Authentication (for specialized requirements)
Each method has its advantages and appropriate use cases. Choose the authentication method that best fits your project's requirements, considering factors like security, client type, and scalability.
Further Resources
- Django REST Framework Authentication Documentation
- Simple JWT Documentation
- OAuth 2.0 Integration
- Two-Factor Authentication
Exercises
- Implement a JWT authentication system with token blacklisting.
- Create a user registration and login API using token authentication.
- Implement role-based permissions (admin, editor, viewer) for a blog API.
- Set up OAuth 2.0 authentication with a provider like Google or GitHub.
- Build an API that uses different authentication schemes for different endpoints.
By mastering authentication in Django REST Framework, you'll be able to create secure APIs that protect sensitive information while providing a smooth experience for valid users.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)