JSON Web Token based authentication for Django REST framework
—
Helper functions and classes for cross-version compatibility and Django integration, including user model handling and field utilities. These utilities ensure the JWT package works across different Django and Django REST Framework versions.
Functions for handling Django user model interactions across different configurations.
def get_username_field():
"""
Returns the username field name from the configured User model.
Returns:
str: Username field name (e.g., 'username', 'email', 'phone')
Notes:
- Uses User.USERNAME_FIELD if available
- Falls back to 'username' if User model is not accessible
- Handles cases where User model is not properly configured
Examples:
- Default Django User: returns 'username'
- Custom User with email auth: returns 'email'
- Custom User with phone auth: returns 'phone'
"""
def get_username(user):
"""
Extracts username value from a user instance.
Args:
user: Django user model instance
Returns:
str: Username value from the user instance
Notes:
- Uses user.get_username() method if available (Django 1.5+)
- Falls back to user.username attribute for older versions
- Handles different User model implementations
Compatibility:
- Django 1.4: Uses user.username attribute
- Django 1.5+: Uses user.get_username() method
"""Compatibility layer for Django REST Framework serializer changes across versions.
class Serializer(serializers.Serializer):
@property
def object(self):
"""
Backward compatibility property for accessing validated data.
Returns:
dict: Validated data from the serializer
Notes:
- In DRF 3.0+: Returns self.validated_data
- Provides compatibility for code expecting .object attribute
- Deprecated in newer DRF versions but maintained for compatibility
"""Custom field classes that ensure consistent behavior across framework versions.
class PasswordField(serializers.CharField):
def __init__(self, *args, **kwargs):
"""
Password input field with consistent styling across DRF versions.
Args:
*args: Positional arguments for CharField
**kwargs: Keyword arguments for CharField
Features:
- Automatically sets input_type to 'password'
- Preserves existing style configuration if provided
- Ensures password fields are rendered with proper input type
Usage:
password = PasswordField(write_only=True, required=True)
"""from rest_framework_jwt.compat import get_username_field, get_username
from django.contrib.auth import get_user_model
User = get_user_model()
# Handle different username field configurations
username_field = get_username_field()
print(f"Authentication uses field: {username_field}")
# Example with email-based authentication
class EmailUser(AbstractUser):
email = models.EmailField(unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
# get_username_field() returns 'email'
# get_username(user_instance) returns user.email valuefrom rest_framework_jwt.compat import Serializer
from rest_framework import serializers
class CustomAuthSerializer(Serializer): # Using compat Serializer
username = serializers.CharField()
password = serializers.CharField()
def validate(self, attrs):
# Validation logic here
return attrs
# Usage that works across DRF versions
serializer = CustomAuthSerializer(data=request.data)
if serializer.is_valid():
# Both work due to compatibility layer:
data = serializer.object # Backward compatibility
data = serializer.validated_data # Modern approachfrom rest_framework_jwt.compat import PasswordField
from rest_framework import serializers
class LoginSerializer(serializers.Serializer):
username = serializers.CharField()
password = PasswordField(write_only=True) # Ensures password input type
# Rendered as <input type="password"> in forms
# Ensures consistent behavior across DRF versionsfrom rest_framework_jwt.compat import get_username, get_username_field
from django.contrib.auth import get_user_model
def create_jwt_payload(user):
"""Create JWT payload compatible with any User model configuration."""
# Get username regardless of field name or Django version
username = get_username(user)
username_field = get_username_field()
payload = {
'user_id': user.pk,
'username': username,
username_field: username, # Include both for flexibility
}
return payload
def authenticate_user(credentials):
"""Authenticate user with dynamic username field."""
User = get_user_model()
username_field = get_username_field()
# Build authentication kwargs dynamically
auth_kwargs = {
username_field: credentials.get(username_field),
'password': credentials.get('password'),
}
return authenticate(**auth_kwargs)from django import forms
from rest_framework_jwt.compat import PasswordField
class JWTAuthForm(forms.Form):
"""Django form using JWT-compatible password field."""
username = forms.CharField(max_length=150)
password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Dynamically set username field label
from rest_framework_jwt.compat import get_username_field
username_field = get_username_field()
if username_field == 'email':
self.fields['username'].label = 'Email Address'
self.fields['username'].widget.attrs['type'] = 'email'
elif username_field == 'phone':
self.fields['username'].label = 'Phone Number'
self.fields['username'].widget.attrs['type'] = 'tel'# Django 1.4 and earlier
class User(models.Model):
username = models.CharField(max_length=30, unique=True)
# get_username() method not available
# Must access username attribute directly
# Django 1.5+
class User(AbstractBaseUser):
USERNAME_FIELD = 'username' # Configurable username field
def get_username(self):
return getattr(self, self.USERNAME_FIELD)
# Custom implementations
class CustomUser(AbstractUser):
USERNAME_FIELD = 'email' # Use email instead of username
def get_username(self):
return self.email# DRF 2.x
class MySerializer(serializers.Serializer):
def restore_object(self, attrs, instance=None):
# Old validation method
pass
# Access via .object attribute
serializer.object
# DRF 3.0+
class MySerializer(serializers.Serializer):
def validate(self, attrs):
# New validation method
return attrs
# Access via .validated_data attribute
serializer.validated_data
# Compatibility layer bridges these differencesdef get_username_field():
try:
# Try to get USERNAME_FIELD from User model
username_field = get_user_model().USERNAME_FIELD
except:
# Fallback to default if User model is not accessible
username_field = 'username'
return username_field
def get_username(user):
try:
# Try modern get_username() method
username = user.get_username()
except AttributeError:
# Fallback to direct attribute access
username = user.username
return username# Issue: Different User model configurations
# Solution: Use get_username_field() and get_username()
# Issue: DRF serializer API changes
# Solution: Use compat.Serializer with .object property
# Issue: Password field rendering inconsistencies
# Solution: Use compat.PasswordField with proper input type
# Issue: Import errors across Django versions
# Solution: Try/except blocks with fallback implementations# Test with different User model configurations
@override_settings(AUTH_USER_MODEL='myapp.EmailUser')
def test_email_based_auth(self):
# Test JWT with email-based user model
pass
@override_settings(AUTH_USER_MODEL='myapp.PhoneUser')
def test_phone_based_auth(self):
# Test JWT with phone-based user model
pass
# Test serializer compatibility
def test_serializer_object_access(self):
serializer = CustomSerializer(data={'test': 'data'})
serializer.is_valid()
# Both should work
data1 = serializer.object
data2 = serializer.validated_data
assert data1 == data2from rest_framework_jwt.compat import get_username_field
class FlexibleJWTSerializer(serializers.Serializer):
"""Serializer that adapts to any User model configuration."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Add username field based on User model
username_field = get_username_field()
self.fields[username_field] = serializers.CharField()
self.fields['password'] = PasswordField(write_only=True)
def validate(self, attrs):
username_field = get_username_field()
username = attrs.get(username_field)
password = attrs.get('password')
if username and password:
user = authenticate(**{
username_field: username,
'password': password
})
if user and user.is_active:
return {'user': user}
raise serializers.ValidationError('Invalid credentials')The compatibility utilities ensure that JWT authentication works reliably across different Django versions, user model configurations, and DRF versions while maintaining clean, maintainable code.
Install with Tessl CLI
npx tessl i tessl/pypi-djangorestframework-jwt