Django's authentication and authorization system provides a robust foundation for managing user identity, permissions, and access control. Understanding how to implement secure authentication flows, manage user permissions, and integrate with external authentication providers is essential for building secure Django applications.
Authentication and authorization are two fundamental security concepts that work together to protect your application:
# Django's built-in authentication system provides:
# 1. User model with authentication
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login, logout
# 2. Permission and group system
from django.contrib.auth.models import Permission, Group
# 3. Decorators and mixins for access control
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
# 4. Views for common authentication flows
from django.contrib.auth.views import LoginView, LogoutView, PasswordChangeView
# Example: Basic authentication flow
def login_user(request):
"""Authenticate and log in a user"""
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
# Authenticate user
user = authenticate(request, username=username, password=password)
if user is not None:
# Log in the user
login(request, user)
return redirect('dashboard')
else:
messages.error(request, 'Invalid credentials')
return render(request, 'login.html')
# Example: Authorization with decorators
@login_required
@permission_required('blog.add_post', raise_exception=True)
def create_post(request):
"""Create a new blog post - requires authentication and permission"""
if request.method == 'POST':
# User is authenticated and has permission
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
return redirect('post_detail', pk=post.pk)
else:
form = PostForm()
return render(request, 'create_post.html', {'form': form})
# Example: Class-based view with authorization
class PostCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""Create view with authentication and permission requirements"""
model = Post
form_class = PostForm
template_name = 'create_post.html'
permission_required = 'blog.add_post'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
# Understanding Django's User model
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
# Get the active User model (important for custom user models)
User = get_user_model()
# User model provides essential fields and methods
class UserExample:
"""Examples of User model usage"""
@staticmethod
def create_user():
"""Create a new user"""
# Method 1: Using create_user (recommended)
user = User.objects.create_user(
username='john_doe',
email='john@example.com',
password='secure_password123',
first_name='John',
last_name='Doe'
)
return user
@staticmethod
def create_superuser():
"""Create a superuser"""
superuser = User.objects.create_superuser(
username='admin',
email='admin@example.com',
password='admin_password123'
)
return superuser
@staticmethod
def user_authentication_methods():
"""Demonstrate user authentication methods"""
# Get user by username
try:
user = User.objects.get(username='john_doe')
# Check if user is active
if user.is_active:
# Check password
if user.check_password('user_password'):
print("Password is correct")
# User status checks
print(f"Is staff: {user.is_staff}")
print(f"Is superuser: {user.is_superuser}")
print(f"Last login: {user.last_login}")
print(f"Date joined: {user.date_joined}")
except User.DoesNotExist:
print("User not found")
@staticmethod
def user_permissions():
"""Work with user permissions"""
user = User.objects.get(username='john_doe')
# Check specific permission
if user.has_perm('blog.add_post'):
print("User can add posts")
# Check multiple permissions
if user.has_perms(['blog.add_post', 'blog.change_post']):
print("User can add and change posts")
# Check permissions for specific object
post = Post.objects.first()
if user.has_perm('blog.change_post', post):
print("User can change this specific post")
# Get all user permissions
user_permissions = user.get_all_permissions()
print(f"User permissions: {user_permissions}")
# Session-based authentication
class SessionAuthentication:
"""Handle session-based authentication"""
@staticmethod
def login_process(request, username, password):
"""Complete login process"""
from django.contrib.auth import authenticate, login
from django.contrib import messages
# Authenticate user
user = authenticate(request, username=username, password=password)
if user is not None:
if user.is_active:
# Log in user (creates session)
login(request, user)
# Optional: Set session data
request.session['login_time'] = timezone.now().isoformat()
request.session['user_preferences'] = {
'theme': 'dark',
'language': 'en'
}
messages.success(request, f'Welcome back, {user.get_full_name() or user.username}!')
return True
else:
messages.error(request, 'Your account is disabled.')
else:
messages.error(request, 'Invalid username or password.')
return False
@staticmethod
def logout_process(request):
"""Complete logout process"""
from django.contrib.auth import logout
from django.contrib import messages
# Get user info before logout
username = request.user.username if request.user.is_authenticated else None
# Log out user (destroys session)
logout(request)
if username:
messages.info(request, f'You have been logged out, {username}.')
return True
@staticmethod
def check_authentication_status(request):
"""Check current authentication status"""
auth_info = {
'is_authenticated': request.user.is_authenticated,
'user': None,
'session_key': request.session.session_key,
'session_data': {}
}
if request.user.is_authenticated:
auth_info['user'] = {
'id': request.user.id,
'username': request.user.username,
'email': request.user.email,
'full_name': request.user.get_full_name(),
'is_staff': request.user.is_staff,
'is_superuser': request.user.is_superuser,
'last_login': request.user.last_login,
}
# Get session data
auth_info['session_data'] = {
'login_time': request.session.get('login_time'),
'user_preferences': request.session.get('user_preferences', {}),
}
return auth_info
# Django's permission system
from django.contrib.auth.models import Permission, Group
from django.contrib.contenttypes.models import ContentType
class PermissionSystem:
"""Understanding Django's permission system"""
@staticmethod
def default_permissions():
"""Django creates default permissions for each model"""
# For a model like Post, Django automatically creates:
# - blog.add_post
# - blog.change_post
# - blog.delete_post
# - blog.view_post (Django 2.1+)
# Get permissions for a model
content_type = ContentType.objects.get_for_model(Post)
permissions = Permission.objects.filter(content_type=content_type)
for perm in permissions:
print(f"Permission: {perm.codename} - {perm.name}")
@staticmethod
def create_custom_permissions():
"""Create custom permissions"""
# Method 1: In model Meta class
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
class Meta:
permissions = [
('can_publish', 'Can publish posts'),
('can_feature', 'Can feature posts'),
('can_moderate', 'Can moderate posts'),
]
# Method 2: Programmatically
content_type = ContentType.objects.get_for_model(Post)
permission, created = Permission.objects.get_or_create(
codename='can_publish',
name='Can publish posts',
content_type=content_type,
)
return permission
@staticmethod
def assign_permissions():
"""Assign permissions to users and groups"""
user = User.objects.get(username='editor')
# Assign permission directly to user
permission = Permission.objects.get(codename='can_publish')
user.user_permissions.add(permission)
# Create group and assign permissions
editors_group, created = Group.objects.get_or_create(name='Editors')
# Add multiple permissions to group
permissions = Permission.objects.filter(
codename__in=['add_post', 'change_post', 'can_publish']
)
editors_group.permissions.set(permissions)
# Add user to group
user.groups.add(editors_group)
return user, editors_group
@staticmethod
def check_permissions():
"""Check user permissions"""
user = User.objects.get(username='editor')
# Check individual permission
if user.has_perm('blog.can_publish'):
print("User can publish posts")
# Check multiple permissions (all must be True)
if user.has_perms(['blog.add_post', 'blog.can_publish']):
print("User can add and publish posts")
# Check if user is in group
if user.groups.filter(name='Editors').exists():
print("User is an editor")
# Get all permissions (direct + group permissions)
all_permissions = user.get_all_permissions()
print(f"All permissions: {all_permissions}")
# Get group permissions only
group_permissions = user.get_group_permissions()
print(f"Group permissions: {group_permissions}")
# Object-level permissions
class ObjectLevelPermissions:
"""Handle object-level permissions"""
@staticmethod
def basic_object_permissions():
"""Basic object-level permission checking"""
user = User.objects.get(username='author')
post = Post.objects.get(pk=1)
# Check if user can change this specific post
# (by default, checks if user has general change permission)
if user.has_perm('blog.change_post', post):
print("User can change this post")
# Custom object permission logic
def can_edit_post(user, post):
"""Custom logic for post editing permissions"""
# Superusers can edit anything
if user.is_superuser:
return True
# Authors can edit their own posts
if post.author == user:
return True
# Editors can edit any post
if user.groups.filter(name='Editors').exists():
return True
# Staff with specific permission
if user.is_staff and user.has_perm('blog.change_post'):
return True
return False
return can_edit_post(user, post)
@staticmethod
def advanced_object_permissions():
"""Advanced object-level permissions with django-guardian"""
# Note: This requires django-guardian package
# pip install django-guardian
try:
from guardian.shortcuts import assign_perm, get_perms, remove_perm
from guardian.decorators import permission_required_or_403
user = User.objects.get(username='collaborator')
post = Post.objects.get(pk=1)
# Assign object-specific permission
assign_perm('change_post', user, post)
# Check object-specific permission
if user.has_perm('blog.change_post', post):
print("User can change this specific post")
# Get all permissions for object
perms = get_perms(user, post)
print(f"User permissions for this post: {perms}")
# Remove object-specific permission
remove_perm('change_post', user, post)
# Decorator for object-level permissions
@permission_required_or_403('blog.change_post', (Post, 'pk', 'post_id'))
def edit_post_view(request, post_id):
post = get_object_or_404(Post, pk=post_id)
# User has permission for this specific post
return render(request, 'edit_post.html', {'post': post})
except ImportError:
print("django-guardian not installed")
return None
# Django provides built-in authentication views
from django.contrib.auth.views import (
LoginView, LogoutView, PasswordChangeView, PasswordChangeDoneView,
PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView,
PasswordResetCompleteView
)
# URL configuration for authentication
from django.urls import path, include
urlpatterns = [
# Use Django's built-in auth URLs
path('accounts/', include('django.contrib.auth.urls')),
# Or define individual views
path('login/', LoginView.as_view(template_name='auth/login.html'), name='login'),
path('logout/', LogoutView.as_view(), name='logout'),
path('password_change/', PasswordChangeView.as_view(), name='password_change'),
path('password_reset/', PasswordResetView.as_view(), name='password_reset'),
]
# Custom authentication views
class CustomLoginView(LoginView):
"""Custom login view with additional functionality"""
template_name = 'auth/login.html'
redirect_authenticated_user = True
def get_success_url(self):
"""Redirect to appropriate page after login"""
# Check for 'next' parameter
next_url = self.request.GET.get('next')
if next_url:
return next_url
# Redirect based on user type
if self.request.user.is_staff:
return reverse('admin_dashboard')
else:
return reverse('user_dashboard')
def form_valid(self, form):
"""Add custom logic on successful login"""
response = super().form_valid(form)
# Log successful login
logger.info(f"User {self.request.user.username} logged in from {self.request.META.get('REMOTE_ADDR')}")
# Set session data
self.request.session['login_timestamp'] = timezone.now().isoformat()
# Add success message
messages.success(self.request, f'Welcome back, {self.request.user.get_full_name() or self.request.user.username}!')
return response
def form_invalid(self, form):
"""Add custom logic on failed login"""
response = super().form_invalid(form)
# Log failed login attempt
username = form.cleaned_data.get('username', 'unknown')
logger.warning(f"Failed login attempt for username: {username} from {self.request.META.get('REMOTE_ADDR')}")
return response
class CustomLogoutView(LogoutView):
"""Custom logout view"""
template_name = 'auth/logout.html'
def dispatch(self, request, *args, **kwargs):
"""Add custom logic before logout"""
if request.user.is_authenticated:
# Log logout
logger.info(f"User {request.user.username} logged out")
# Clear custom session data
request.session.pop('user_preferences', None)
request.session.pop('login_timestamp', None)
return super().dispatch(request, *args, **kwargs)
# Custom authentication forms
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
class CustomAuthenticationForm(AuthenticationForm):
"""Custom login form with additional validation"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Customize form fields
self.fields['username'].widget.attrs.update({
'class': 'form-control',
'placeholder': 'Username or Email'
})
self.fields['password'].widget.attrs.update({
'class': 'form-control',
'placeholder': 'Password'
})
def clean(self):
"""Add custom validation"""
cleaned_data = super().clean()
# Add rate limiting check
username = cleaned_data.get('username')
if username:
# Check for too many failed attempts (implement rate limiting)
failed_attempts = cache.get(f'failed_login_{username}', 0)
if failed_attempts >= 5:
raise forms.ValidationError(
"Too many failed login attempts. Please try again later."
)
return cleaned_data
class CustomUserCreationForm(UserCreationForm):
"""Custom user registration form"""
email = forms.EmailField(required=True)
first_name = forms.CharField(max_length=30, required=True)
last_name = forms.CharField(max_length=30, required=True)
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2')
def clean_email(self):
"""Validate email uniqueness"""
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError("A user with this email already exists.")
return email
def save(self, commit=True):
"""Save user with additional fields"""
user = super().save(commit=False)
user.email = self.cleaned_data['email']
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
if commit:
user.save()
return user
class AuthenticationSecurity:
"""Security best practices for authentication"""
@staticmethod
def password_security():
"""Password security recommendations"""
# Django settings for password security
password_settings = {
'AUTH_PASSWORD_VALIDATORS': [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 12,
}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
],
# Password hashing
'PASSWORD_HASHERS': [
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]
}
return password_settings
@staticmethod
def session_security():
"""Session security settings"""
session_settings = {
# Session cookie security
'SESSION_COOKIE_SECURE': True, # HTTPS only
'SESSION_COOKIE_HTTPONLY': True, # No JavaScript access
'SESSION_COOKIE_SAMESITE': 'Strict', # CSRF protection
'SESSION_COOKIE_AGE': 3600, # 1 hour
# CSRF protection
'CSRF_COOKIE_SECURE': True,
'CSRF_COOKIE_HTTPONLY': True,
'CSRF_COOKIE_SAMESITE': 'Strict',
# Additional security
'SECURE_BROWSER_XSS_FILTER': True,
'SECURE_CONTENT_TYPE_NOSNIFF': True,
'X_FRAME_OPTIONS': 'DENY',
}
return session_settings
@staticmethod
def implement_rate_limiting():
"""Implement rate limiting for authentication"""
from django.core.cache import cache
from django.http import HttpResponseTooManyRequests
def rate_limit_decorator(max_attempts=5, window=300):
"""Rate limiting decorator"""
def decorator(view_func):
def wrapper(request, *args, **kwargs):
# Get client IP
client_ip = request.META.get('REMOTE_ADDR')
cache_key = f'rate_limit_{client_ip}'
# Get current attempts
attempts = cache.get(cache_key, 0)
if attempts >= max_attempts:
return HttpResponseTooManyRequests(
"Too many requests. Please try again later."
)
# Increment attempts
cache.set(cache_key, attempts + 1, window)
return view_func(request, *args, **kwargs)
return wrapper
return decorator
return rate_limit_decorator
@staticmethod
def two_factor_authentication():
"""Basic two-factor authentication implementation"""
import pyotp
import qrcode
from io import BytesIO
import base64
class TwoFactorAuth:
"""Two-factor authentication helper"""
@staticmethod
def generate_secret():
"""Generate TOTP secret for user"""
return pyotp.random_base32()
@staticmethod
def get_qr_code(user, secret):
"""Generate QR code for TOTP setup"""
totp_uri = pyotp.totp.TOTP(secret).provisioning_uri(
name=user.email,
issuer_name="Your App Name"
)
# Generate QR code
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(totp_uri)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
# Convert to base64 for display
buffer = BytesIO()
img.save(buffer, format='PNG')
buffer.seek(0)
return base64.b64encode(buffer.getvalue()).decode()
@staticmethod
def verify_token(secret, token):
"""Verify TOTP token"""
totp = pyotp.TOTP(secret)
return totp.verify(token, valid_window=1)
return TwoFactorAuth
Django's authentication and authorization system provides a solid foundation for securing your application. Understanding these core concepts, implementing proper security measures, and following best practices ensures your users' data and your application remain protected.
Django Serialization Framework
Django's serialization framework provides a mechanism for translating Django models into other formats like JSON, XML, or YAML. This is essential for creating APIs, data exports, fixtures, and data interchange between systems.
Overview of Django's Authentication System
Django's authentication system is a comprehensive framework that handles user authentication, authorization, and session management out of the box. Understanding its architecture, components, and workflow is essential for building secure Django applications.