Django SDK for Auth Service
Project description
Auth Service Django SDK
A comprehensive Django integration for Auth Service providing secure authentication, authorization, and permission management.
Features
- 🔐 Django Authentication Backend: Seamless integration with Django's auth system
- 🛡️ Middleware Support: Automatic token extraction and validation
- 🎯 Decorators & Mixins: Easy protection for views and APIs
- 🚀 DRF Integration: Full Django REST Framework support
- ⚡ Caching: Built-in caching with Django's cache framework
- 🔄 Async Support: Django 4.1+ async view support
- 📊 Type Safety: Full type hints for better IDE support
- 🔐 MFA Support: Two-factor authentication integration
Installation
pip install authservice-django
For Django REST Framework support:
pip install authservice-django[drf]
For async support:
pip install authservice-django[async]
Quick Start
1. Configure Django Settings
# settings.py
# Add to installed apps
INSTALLED_APPS = [
# ...
'authservice_django',
]
# Add authentication backend
AUTHENTICATION_BACKENDS = [
'authservice_django.backends.AuthServiceBackend',
'django.contrib.auth.backends.ModelBackend', # Keep for admin
]
# Add middleware
MIDDLEWARE = [
# ...
'django.contrib.auth.middleware.AuthenticationMiddleware',
'authservice_django.middleware.AuthServiceMiddleware',
]
# Auth Service configuration
AUTH_SERVICE_URL = 'https://auth.example.com'
AUTH_SERVICE_APP_ID = 'your-app-id'
AUTH_SERVICE_APP_SECRET = 'your-app-secret'
# Optional settings
AUTH_SERVICE_CACHE_ENABLED = True
AUTH_SERVICE_CACHE_TTL = 300 # 5 minutes
AUTH_SERVICE_CREATE_USERS = True # Auto-create users
AUTH_SERVICE_UPDATE_USERS = True # Update user info
AUTH_SERVICE_LOGIN_URL = '/login/'
AUTH_SERVICE_UNAUTHORIZED_URL = '/unauthorized/'
2. Protect Views with Decorators
from authservice_django import require_auth, require_permission
@require_auth
def profile_view(request):
return render(request, 'profile.html', {'user': request.user})
@require_permission('posts:create')
def create_post_view(request):
# User has permission to create posts
return render(request, 'create_post.html')
@require_any_permission(['posts:edit', 'posts:admin'])
def edit_post_view(request, post_id):
# User has either edit or admin permission
return render(request, 'edit_post.html')
3. Protect Class-Based Views
from django.views.generic import CreateView, UpdateView
from authservice_django import LoginRequiredMixin, PermissionRequiredMixin
class CreatePostView(PermissionRequiredMixin, CreateView):
model = Post
permission_required = 'posts:create'
template_name = 'create_post.html'
class EditPostView(PermissionRequiredMixin, UpdateView):
model = Post
permission_required = ['posts:edit', 'posts:admin'] # Requires all
template_name = 'edit_post.html'
Django REST Framework Integration
Configure DRF Settings
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'authservice_django.drf.AuthServiceAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'authservice_django.drf.IsAuthenticated',
],
}
Protect API Views
from rest_framework import viewsets
from authservice_django.drf import HasPermission, HasAnyPermission
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [HasPermission]
def get_required_permission(self):
"""Dynamic permissions based on action"""
if self.action == 'create':
return 'posts:create'
elif self.action in ['update', 'partial_update']:
return 'posts:edit'
elif self.action == 'destroy':
return 'posts:delete'
return None # Read is allowed for authenticated users
Custom Permission Classes
from authservice_django.drf import BaseAuthServicePermission
class IsPostOwner(BaseAuthServicePermission):
def has_object_permission(self, request, view, obj):
# Check if user owns the post
return obj.author == request.user
class CanModerate(BaseAuthServicePermission):
def has_permission(self, request, view):
token = self.get_auth_token(request)
if not token:
return False
return self.client.check_permission(token, 'posts:moderate')
Multi-Factor Authentication (MFA)
The Django SDK is fully MFA-aware. When users have MFA enabled, they must complete MFA verification before accessing protected resources.
MFA Status Check
from authservice_django import AuthServiceClient
@require_auth
def account_security_view(request):
"""Display user's MFA status"""
client = AuthServiceClient()
# Get MFA status - middleware ensures token is valid
mfa_status = client.get_user_mfa_status(request.auth_token)
return render(request, 'account/security.html', {
'mfa_enabled': mfa_status['enabled'],
'backup_codes_remaining': mfa_status.get('backupCodesRemaining', 0)
})
MFA-Protected Views
The SDK automatically enforces MFA. If a user has MFA enabled but hasn't completed it:
@require_auth
def sensitive_data_view(request):
"""
This view is automatically protected by MFA.
Users with MFA enabled must complete verification first.
"""
# If we reach here, user has valid auth AND completed MFA if enabled
return render(request, 'sensitive_data.html')
Handling MFA in Login Flow
from authservice_django import AuthServiceClient, AuthServiceError
def custom_login_view(request):
if request.method == 'POST':
email = request.POST.get('email')
password = request.POST.get('password')
mfa_code = request.POST.get('mfa_code')
client = AuthServiceClient()
try:
# Login with optional MFA code
if mfa_code:
# User provided MFA code
result = client.login_user(email, password, mfa_code=mfa_code)
else:
# Initial login attempt
result = client.login_user(email, password)
# Success - set token in session/cookie
request.session['auth_token'] = result['accessToken']
return redirect('dashboard')
except AuthServiceError as e:
if e.code == 'MFA_REQUIRED':
# User needs to provide MFA code
return render(request, 'login.html', {
'show_mfa': True,
'email': email,
'error': 'Please enter your 2FA code'
})
else:
# Other error
return render(request, 'login.html', {
'error': str(e)
})
return render(request, 'login.html')
MFA Webhook Events
from django.views.decorators.csrf import csrf_exempt
from authservice_django import AuthServiceClient
@csrf_exempt
def mfa_webhook_handler(request):
"""Handle MFA-related webhook events"""
if request.method != 'POST':
return HttpResponse(status=405)
client = AuthServiceClient()
# Verify webhook signature
signature = request.headers.get('X-Auth-Signature')
timestamp = request.headers.get('X-Auth-Timestamp')
if not client.verify_webhook_signature(signature, timestamp, request.body):
return HttpResponse(status=401)
event = json.loads(request.body)
event_type = event.get('type')
event_data = event.get('data', {})
# Handle MFA events
if event_type == 'user.mfa.enabled':
# User enabled MFA
user_id = event_data['userId']
# Send congratulatory email
send_mfa_enabled_email(user_id)
# Log security event
log_security_event('mfa_enabled', user_id)
elif event_type == 'user.mfa.disabled':
# User disabled MFA - security concern
user_id = event_data['userId']
# Send warning email
send_mfa_disabled_warning(user_id)
# Log security event
log_security_event('mfa_disabled', user_id)
elif event_type == 'user.mfa.backup_codes.regenerated':
# Backup codes regenerated
user_id = event_data['userId']
# Log for audit trail
log_security_event('backup_codes_regenerated', user_id)
# Let SDK handle cache invalidation
client.handle_mfa_webhook(event_type, event_data)
return HttpResponse(status=200)
MFA in Templates
{# security_settings.html #}
{% load authservice_tags %}
<h2>Two-Factor Authentication</h2>
{% if mfa_enabled %}
<div class="alert alert-success">
<strong>2FA is enabled</strong>
<p>Your account is protected with two-factor authentication.</p>
<p>Backup codes remaining: {{ backup_codes_remaining }}</p>
</div>
<div class="actions">
<a href="{% url 'regenerate-backup-codes' %}" class="btn btn-secondary">
Regenerate Backup Codes
</a>
<a href="{% url 'disable-mfa' %}" class="btn btn-danger">
Disable 2FA
</a>
</div>
{% else %}
<div class="alert alert-warning">
<strong>2FA is not enabled</strong>
<p>Add an extra layer of security to your account.</p>
</div>
<a href="{% url 'setup-mfa' %}" class="btn btn-primary">
Enable Two-Factor Authentication
</a>
{% endif %}
MFA Best Practices
- Always use MFA for admin/privileged users
- Provide clear instructions during MFA setup
- Store backup codes securely
- Log all MFA-related events for security auditing
- Send notifications when MFA status changes
- Consider requiring MFA for sensitive operations
- Test MFA flow thoroughly including edge cases
MFA Error Handling
from authservice_django import AuthServiceError
try:
# MFA operations
mfa_status = client.get_user_mfa_status(token)
except AuthServiceError as e:
if e.code == 'TOKEN_EXPIRED':
# Token expired - redirect to login
return redirect('login')
elif e.code == 'MFA_REQUIRED':
# MFA verification needed
return redirect('mfa-verify')
else:
# Handle other errors
logger.error(f"MFA error: {e}")
return render(request, 'error.html')
Advanced Usage
Custom User Model
# models.py
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
auth_service_id = models.CharField(max_length=255, unique=True)
# ... other fields
# settings.py
AUTH_USER_MODEL = 'myapp.CustomUser'
AUTH_SERVICE_USER_MODEL = 'myapp.CustomUser'
Permission Mapping
Map Auth Service permissions to Django permissions:
# settings.py
AUTH_SERVICE_PERMISSION_MAP = {
'posts:create': 'blog.add_post',
'posts:edit': 'blog.change_post',
'posts:delete': 'blog.delete_post',
'posts:read': 'blog.view_post',
}
Webhook Integration
from authservice_django import AuthServiceClient
def webhook_view(request):
client = AuthServiceClient()
# Verify webhook signature
signature = request.headers.get('X-Webhook-Signature')
timestamp = request.headers.get('X-Webhook-Timestamp')
if not client.verify_webhook_signature(
signature, timestamp, request.body
):
return HttpResponse(status=401)
# Process webhook
data = json.loads(request.body)
if data['event'] == 'permission.changed':
# Invalidate cache for user
client.invalidate_user_cache(data['userId'])
return HttpResponse(status=200)
Async Views (Django 4.1+)
from authservice_django import require_auth_async, require_permission_async
@require_auth_async
async def async_profile_view(request):
# Async view with authentication
return JsonResponse({'user_id': request.auth_user['id']})
@require_permission_async('posts:create')
async def async_create_post(request):
# Async view with permission check
data = await request.json()
# ... create post
return JsonResponse({'status': 'created'})
Template Tags
{% load authservice_tags %}
{% if user|has_permission:"posts:create" %}
<a href="{% url 'create_post' %}">Create Post</a>
{% endif %}
{% if user|has_any_permission:"posts:edit,posts:admin" %}
<a href="{% url 'edit_post' post.id %}">Edit</a>
{% endif %}
{% if user|has_role:"moderator" %}
<div class="moderation-tools">...</div>
{% endif %}
Management Commands
# Sync permissions from Auth Service
python manage.py sync_auth_permissions
# Clear auth cache
python manage.py clear_auth_cache
# Verify auth service connection
python manage.py check_auth_service
Caching
The SDK uses Django's cache framework. Configure caching in settings:
# Use Redis for better performance
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
}
}
# Cache settings
AUTH_SERVICE_CACHE_ENABLED = True
AUTH_SERVICE_CACHE_TTL = 300 # 5 minutes
AUTH_SERVICE_CACHE_PREFIX = 'authservice'
Error Handling
from authservice_django import (
AuthServiceError,
TokenError,
PermissionError,
NetworkError
)
try:
# Your auth code
except TokenError as e:
# Invalid or expired token
logger.error(f"Token error: {e}")
except PermissionError as e:
# Permission denied
logger.warning(f"Permission denied: {e.required_permissions}")
except NetworkError as e:
# Network/connection issues
logger.error(f"Network error: {e}")
except AuthServiceError as e:
# General auth service error
logger.error(f"Auth error: {e.code} - {e.message}")
Testing
Mock Authentication in Tests
from django.test import TestCase, Client
from authservice_django.testing import AuthServiceTestMixin
class PostViewTests(AuthServiceTestMixin, TestCase):
def setUp(self):
self.client = Client()
self.user = self.create_test_user(
permissions=['posts:create', 'posts:edit']
)
def test_create_post_with_permission(self):
self.authenticate_user(self.user)
response = self.client.post('/posts/create/', {
'title': 'Test Post',
'content': 'Test content'
})
self.assertEqual(response.status_code, 201)
def test_create_post_without_permission(self):
user = self.create_test_user(permissions=[])
self.authenticate_user(user)
response = self.client.post('/posts/create/', {
'title': 'Test Post',
'content': 'Test content'
})
self.assertEqual(response.status_code, 403)
Test Settings
# test_settings.py
from .settings import *
# Disable auth service for tests
AUTH_SERVICE_URL = 'http://mock-auth-service'
AUTH_SERVICE_CACHE_ENABLED = False
# Use in-memory cache for tests
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
}
}
Debugging
Enable debug logging:
# settings.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'authservice_django': {
'handlers': ['console'],
'level': 'DEBUG',
},
},
}
Best Practices
- Always use HTTPS for AUTH_SERVICE_URL in production
- Keep APP_SECRET secure - use environment variables
- Enable caching for better performance
- Use specific permissions rather than broad ones
- Implement proper error handling for network failures
- Regular cache invalidation for permission changes
- Monitor auth service health and response times
Migration from Django Auth
# management/commands/migrate_to_authservice.py
from django.core.management.base import BaseCommand
from django.contrib.auth import get_user_model
from authservice_django import AuthServiceClient
class Command(BaseCommand):
def handle(self, *args, **options):
User = get_user_model()
client = AuthServiceClient()
for user in User.objects.all():
# Create user in auth service
# Map Django permissions to Auth Service
# Update user with auth_service_id
pass
Troubleshooting
Common Issues
-
"AUTH_SERVICE_URL is required"
- Ensure AUTH_SERVICE_URL is set in settings.py
- Check for typos in environment variables
-
Permission always denied
- Verify token is being passed correctly
- Check permission format (resource:action)
- Ensure user has permission in Auth Service
-
Users not created automatically
- Set AUTH_SERVICE_CREATE_USERS = True
- Ensure User model has required fields
-
Cache not working
- Verify cache backend is configured
- Check AUTH_SERVICE_CACHE_ENABLED = True
- Monitor cache hit/miss rates
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file authservice_django-1.0.0.tar.gz.
File metadata
- Download URL: authservice_django-1.0.0.tar.gz
- Upload date:
- Size: 31.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3bb8d5348139850c408a410699352ee2adcee40f58cf80235f53f15af8abaa3f
|
|
| MD5 |
ee18c67fb09c1bab23448d64638378b1
|
|
| BLAKE2b-256 |
9e7c2c97bee50fd109513d7588bf284225046efd212e2b6fd6069c4cf66f551e
|
File details
Details for the file authservice_django-1.0.0-py3-none-any.whl.
File metadata
- Download URL: authservice_django-1.0.0-py3-none-any.whl
- Upload date:
- Size: 27.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3c633fc00397ac91fa9e975302b31b04cf17937b0d6844619ef599fa404bc841
|
|
| MD5 |
b5682ef378d2231e94a385d0312775cb
|
|
| BLAKE2b-256 |
edf6eca3d656ce315355783d021c7139f18f28c3148cccb9336952698f2f8a27
|