Authentication and Authorization

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.

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.

Authentication System Architecture

Core Components

# Django's authentication system consists of several key components:

# 1. User Model - Represents users in the system
from django.contrib.auth.models import User, AbstractUser, AbstractBaseUser

# 2. Authentication Backends - Handle the authentication logic
from django.contrib.auth.backends import ModelBackend

# 3. Middleware - Manages sessions and user context
from django.contrib.auth.middleware import AuthenticationMiddleware

# 4. Views and Forms - Provide authentication interfaces
from django.contrib.auth.views import LoginView, LogoutView
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm

# 5. Decorators and Mixins - Protect views and enforce permissions
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.mixins import LoginRequiredMixin

# System overview example
class AuthenticationSystemOverview:
    """Comprehensive overview of Django's authentication system"""
    
    def __init__(self):
        self.components = {
            'user_model': 'Stores user data and credentials',
            'authentication_backends': 'Verify user credentials',
            'middleware': 'Manages sessions and request.user',
            'permissions': 'Control access to resources',
            'groups': 'Organize users and permissions',
            'sessions': 'Maintain user state across requests',
        }
    
    def get_system_info(self):
        """Get information about the authentication system"""
        
        from django.conf import settings
        from django.contrib.auth import get_user_model
        
        User = get_user_model()
        
        system_info = {
            'user_model': f"{User._meta.app_label}.{User._meta.object_name}",
            'authentication_backends': settings.AUTHENTICATION_BACKENDS,
            'login_url': settings.LOGIN_URL,
            'login_redirect_url': getattr(settings, 'LOGIN_REDIRECT_URL', '/'),
            'logout_redirect_url': getattr(settings, 'LOGOUT_REDIRECT_URL', None),
            'session_engine': settings.SESSION_ENGINE,
            'session_cookie_age': settings.SESSION_COOKIE_AGE,
        }
        
        return system_info
    
    def demonstrate_authentication_flow(self):
        """Demonstrate the complete authentication flow"""
        
        flow_steps = [
            "1. User submits credentials via login form",
            "2. Django validates credentials using authentication backend",
            "3. If valid, user object is retrieved and session is created",
            "4. AuthenticationMiddleware sets request.user for subsequent requests",
            "5. Views can check authentication status and permissions",
            "6. User can be logged out, destroying the session"
        ]
        
        return flow_steps

# Authentication workflow demonstration
def authentication_workflow_example(request):
    """Complete example of authentication workflow"""
    
    from django.contrib.auth import authenticate, login, logout
    from django.contrib import messages
    from django.shortcuts import render, redirect
    
    if request.method == 'POST':
        action = request.POST.get('action')
        
        if action == 'login':
            # Step 1: Get credentials
            username = request.POST.get('username')
            password = request.POST.get('password')
            
            # Step 2: Authenticate user
            user = authenticate(request, username=username, password=password)
            
            if user is not None:
                # Step 3: Check if user is active
                if user.is_active:
                    # Step 4: Log in user (create session)
                    login(request, user)
                    
                    # Step 5: Set success message
                    messages.success(request, f'Welcome, {user.get_full_name() or user.username}!')
                    
                    # Step 6: Redirect to appropriate page
                    next_url = request.POST.get('next') or request.GET.get('next')
                    if next_url:
                        return redirect(next_url)
                    else:
                        return redirect('dashboard')
                else:
                    messages.error(request, 'Your account is disabled.')
            else:
                messages.error(request, 'Invalid username or password.')
        
        elif action == 'logout':
            # Logout process
            if request.user.is_authenticated:
                username = request.user.username
                logout(request)
                messages.info(request, f'You have been logged out, {username}.')
            
            return redirect('login')
    
    # Render login form
    context = {
        'user_authenticated': request.user.is_authenticated,
        'user_info': {
            'username': request.user.username if request.user.is_authenticated else None,
            'is_staff': request.user.is_staff if request.user.is_authenticated else False,
            'permissions': list(request.user.get_all_permissions()) if request.user.is_authenticated else [],
        }
    }
    
    return render(request, 'auth/login.html', context)

Authentication Backends

# Understanding authentication backends
from django.contrib.auth.backends import BaseBackend, ModelBackend
from django.contrib.auth import get_user_model
from django.db.models import Q

User = get_user_model()

class CustomAuthenticationBackend(BaseBackend):
    """Custom authentication backend example"""
    
    def authenticate(self, request, username=None, password=None, **kwargs):
        """
        Authenticate user with custom logic
        
        This backend allows login with username or email
        """
        
        if username is None or password is None:
            return None
        
        try:
            # Try to find user by username or email
            user = User.objects.get(
                Q(username=username) | Q(email=username)
            )
            
            # Check password
            if user.check_password(password):
                return user
            
        except User.DoesNotExist:
            # Run default password hasher to prevent timing attacks
            User().set_password(password)
            return None
        
        return None
    
    def get_user(self, user_id):
        """Get user by ID"""
        
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

class LDAPAuthenticationBackend(BaseBackend):
    """LDAP authentication backend example"""
    
    def authenticate(self, request, username=None, password=None, **kwargs):
        """Authenticate against LDAP server"""
        
        import ldap
        from django.conf import settings
        
        if not username or not password:
            return None
        
        try:
            # LDAP connection settings
            ldap_server = getattr(settings, 'LDAP_SERVER', 'ldap://localhost')
            ldap_base_dn = getattr(settings, 'LDAP_BASE_DN', 'dc=example,dc=com')
            
            # Connect to LDAP
            conn = ldap.initialize(ldap_server)
            conn.protocol_version = ldap.VERSION3
            
            # Bind with user credentials
            user_dn = f"uid={username},{ldap_base_dn}"
            conn.simple_bind_s(user_dn, password)
            
            # Search for user attributes
            search_filter = f"(uid={username})"
            attributes = ['cn', 'mail', 'givenName', 'sn']
            
            result = conn.search_s(ldap_base_dn, ldap.SCOPE_SUBTREE, search_filter, attributes)
            
            if result:
                # Get user attributes
                dn, attrs = result[0]
                
                # Get or create Django user
                user, created = User.objects.get_or_create(
                    username=username,
                    defaults={
                        'email': attrs.get('mail', [b''])[0].decode('utf-8'),
                        'first_name': attrs.get('givenName', [b''])[0].decode('utf-8'),
                        'last_name': attrs.get('sn', [b''])[0].decode('utf-8'),
                    }
                )
                
                return user
        
        except ldap.INVALID_CREDENTIALS:
            return None
        except Exception as e:
            # Log error
            import logging
            logger = logging.getLogger(__name__)
            logger.error(f"LDAP authentication error: {e}")
            return None
        
        finally:
            try:
                conn.unbind()
            except:
                pass
        
        return None
    
    def get_user(self, user_id):
        """Get user by ID"""
        
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

class TokenAuthenticationBackend(BaseBackend):
    """Token-based authentication backend"""
    
    def authenticate(self, request, token=None, **kwargs):
        """Authenticate using API token"""
        
        if not token:
            return None
        
        try:
            # Import your token model
            from myapp.models import APIToken
            
            api_token = APIToken.objects.select_related('user').get(
                token=token,
                is_active=True,
                expires_at__gt=timezone.now()
            )
            
            return api_token.user
        
        except APIToken.DoesNotExist:
            return None
    
    def get_user(self, user_id):
        """Get user by ID"""
        
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

# Backend configuration in settings.py
AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.ModelBackend',  # Default backend
    'myapp.backends.CustomAuthenticationBackend',  # Custom backend
    'myapp.backends.LDAPAuthenticationBackend',    # LDAP backend
    'myapp.backends.TokenAuthenticationBackend',   # Token backend
]

Session Management

# Understanding Django's session system
from django.contrib.sessions.models import Session
from django.contrib.auth import get_user_model
from django.utils import timezone

User = get_user_model()

class SessionManager:
    """Manage user sessions"""
    
    @staticmethod
    def get_active_sessions():
        """Get all active sessions"""
        
        active_sessions = Session.objects.filter(
            expire_date__gt=timezone.now()
        )
        
        session_data = []
        
        for session in active_sessions:
            try:
                # Decode session data
                data = session.get_decoded()
                user_id = data.get('_auth_user_id')
                
                if user_id:
                    try:
                        user = User.objects.get(pk=user_id)
                        session_data.append({
                            'session_key': session.session_key,
                            'user': user,
                            'expire_date': session.expire_date,
                            'data': data
                        })
                    except User.DoesNotExist:
                        pass
            
            except Exception:
                # Skip invalid sessions
                continue
        
        return session_data
    
    @staticmethod
    def get_user_sessions(user):
        """Get all sessions for a specific user"""
        
        user_sessions = []
        
        # Get all active sessions
        active_sessions = Session.objects.filter(
            expire_date__gt=timezone.now()
        )
        
        for session in active_sessions:
            try:
                data = session.get_decoded()
                session_user_id = data.get('_auth_user_id')
                
                if session_user_id and int(session_user_id) == user.id:
                    user_sessions.append({
                        'session_key': session.session_key,
                        'expire_date': session.expire_date,
                        'login_time': data.get('login_time'),
                        'ip_address': data.get('ip_address'),
                        'user_agent': data.get('user_agent'),
                    })
            
            except Exception:
                continue
        
        return user_sessions
    
    @staticmethod
    def terminate_user_sessions(user, exclude_current=None):
        """Terminate all sessions for a user"""
        
        terminated_count = 0
        
        # Get all active sessions
        active_sessions = Session.objects.filter(
            expire_date__gt=timezone.now()
        )
        
        for session in active_sessions:
            try:
                data = session.get_decoded()
                session_user_id = data.get('_auth_user_id')
                
                if session_user_id and int(session_user_id) == user.id:
                    # Skip current session if specified
                    if exclude_current and session.session_key == exclude_current:
                        continue
                    
                    session.delete()
                    terminated_count += 1
            
            except Exception:
                continue
        
        return terminated_count
    
    @staticmethod
    def enhance_session_security(request):
        """Add security enhancements to session"""
        
        if request.user.is_authenticated:
            # Store security information
            request.session['login_time'] = timezone.now().isoformat()
            request.session['ip_address'] = request.META.get('REMOTE_ADDR')
            request.session['user_agent'] = request.META.get('HTTP_USER_AGENT', '')[:200]
            
            # Set session expiry based on user preferences
            if request.POST.get('remember_me'):
                # Remember for 30 days
                request.session.set_expiry(30 * 24 * 60 * 60)
            else:
                # Browser session only
                request.session.set_expiry(0)
    
    @staticmethod
    def validate_session_security(request):
        """Validate session security"""
        
        if not request.user.is_authenticated:
            return True
        
        # Check IP address consistency
        stored_ip = request.session.get('ip_address')
        current_ip = request.META.get('REMOTE_ADDR')
        
        if stored_ip and stored_ip != current_ip:
            # IP changed - potential security issue
            from django.contrib.auth import logout
            logout(request)
            return False
        
        # Check user agent consistency
        stored_ua = request.session.get('user_agent', '')
        current_ua = request.META.get('HTTP_USER_AGENT', '')[:200]
        
        if stored_ua and stored_ua != current_ua:
            # User agent changed - potential security issue
            from django.contrib.auth import logout
            logout(request)
            return False
        
        return True

# Custom session middleware
class SecuritySessionMiddleware:
    """Enhanced session security middleware"""
    
    def __init__(self, get_response):
        self.get_response = get_response
    
    def __call__(self, request):
        # Validate session security before processing request
        if hasattr(request, 'session') and hasattr(request, 'user'):
            if not SessionManager.validate_session_security(request):
                # Session invalidated due to security concerns
                from django.contrib import messages
                messages.warning(request, 'Your session was terminated for security reasons.')
        
        response = self.get_response(request)
        
        # Enhance session security after login
        if (hasattr(request, 'user') and request.user.is_authenticated and 
            request.method == 'POST' and 'login' in request.path):
            SessionManager.enhance_session_security(request)
        
        return response

User Model Integration

# Understanding the User model and its integration
from django.contrib.auth.models import AbstractUser, AbstractBaseUser, PermissionsMixin
from django.contrib.auth import get_user_model

class UserModelIntegration:
    """Understanding User model integration"""
    
    @staticmethod
    def get_user_model_info():
        """Get information about the current User model"""
        
        User = get_user_model()
        
        model_info = {
            'model_class': f"{User._meta.app_label}.{User._meta.object_name}",
            'fields': [field.name for field in User._meta.fields],
            'required_fields': User.REQUIRED_FIELDS,
            'username_field': User.USERNAME_FIELD,
            'is_abstract': User._meta.abstract,
        }
        
        return model_info
    
    @staticmethod
    def demonstrate_user_methods():
        """Demonstrate important User model methods"""
        
        User = get_user_model()
        
        # Create user
        user = User.objects.create_user(
            username='demo_user',
            email='demo@example.com',
            password='secure_password123'
        )
        
        # User authentication methods
        methods_demo = {
            'check_password': user.check_password('secure_password123'),
            'set_password': 'user.set_password("new_password")',
            'has_usable_password': user.has_usable_password(),
            'get_full_name': user.get_full_name(),
            'get_short_name': user.get_short_name(),
            'email_user': 'user.email_user("Subject", "Message")',
        }
        
        # Permission methods
        permission_methods = {
            'has_perm': 'user.has_perm("app.permission_name")',
            'has_perms': 'user.has_perms(["app.perm1", "app.perm2"])',
            'has_module_perms': 'user.has_module_perms("app")',
            'get_all_permissions': user.get_all_permissions(),
            'get_group_permissions': user.get_group_permissions(),
        }
        
        # Status methods
        status_methods = {
            'is_authenticated': user.is_authenticated,
            'is_anonymous': user.is_anonymous,
            'is_active': user.is_active,
            'is_staff': user.is_staff,
            'is_superuser': user.is_superuser,
        }
        
        return {
            'methods': methods_demo,
            'permissions': permission_methods,
            'status': status_methods
        }

# Custom User model examples
class CustomUser(AbstractUser):
    """Extended User model with additional fields"""
    
    phone_number = models.CharField(max_length=15, blank=True)
    date_of_birth = models.DateField(null=True, blank=True)
    profile_picture = models.ImageField(upload_to='profiles/', blank=True)
    is_verified = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    def get_age(self):
        """Calculate user's age"""
        if self.date_of_birth:
            from datetime import date
            today = date.today()
            return today.year - self.date_of_birth.year - (
                (today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day)
            )
        return None
    
    def get_profile_picture_url(self):
        """Get profile picture URL or default"""
        if self.profile_picture:
            return self.profile_picture.url
        return '/static/images/default-avatar.png'

class EmailUser(AbstractBaseUser, PermissionsMixin):
    """User model that uses email as username"""
    
    email = models.EmailField(unique=True)
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    date_joined = models.DateTimeField(auto_now_add=True)
    
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name', 'last_name']
    
    objects = EmailUserManager()  # Custom manager required
    
    def get_full_name(self):
        return f"{self.first_name} {self.last_name}".strip()
    
    def get_short_name(self):
        return self.first_name

from django.contrib.auth.models import BaseUserManager

class EmailUserManager(BaseUserManager):
    """Manager for EmailUser model"""
    
    def create_user(self, email, first_name, last_name, password=None, **extra_fields):
        """Create and return a regular user"""
        
        if not email:
            raise ValueError('Email is required')
        
        email = self.normalize_email(email)
        user = self.model(
            email=email,
            first_name=first_name,
            last_name=last_name,
            **extra_fields
        )
        user.set_password(password)
        user.save(using=self._db)
        return user
    
    def create_superuser(self, email, first_name, last_name, password=None, **extra_fields):
        """Create and return a superuser"""
        
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        
        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True')
        
        return self.create_user(email, first_name, last_name, password, **extra_fields)

Authentication Middleware

# Understanding authentication middleware
from django.contrib.auth.middleware import AuthenticationMiddleware
from django.contrib.auth import get_user
from django.utils.functional import SimpleLazyObject

class CustomAuthenticationMiddleware(AuthenticationMiddleware):
    """Enhanced authentication middleware"""
    
    def process_request(self, request):
        """Process request and set user"""
        
        # Call parent method to set basic user
        super().process_request(request)
        
        # Add custom user enhancements
        if hasattr(request, 'user') and request.user.is_authenticated:
            # Add user preferences to request
            request.user_preferences = self.get_user_preferences(request.user)
            
            # Add user permissions cache
            request.user_permissions = self.get_cached_permissions(request.user)
    
    def get_user_preferences(self, user):
        """Get user preferences"""
        
        # This could come from a UserProfile model or cache
        return {
            'theme': 'light',
            'language': 'en',
            'timezone': 'UTC',
        }
    
    def get_cached_permissions(self, user):
        """Get cached user permissions"""
        
        from django.core.cache import cache
        
        cache_key = f'user_permissions_{user.id}'
        permissions = cache.get(cache_key)
        
        if permissions is None:
            permissions = list(user.get_all_permissions())
            cache.set(cache_key, permissions, 300)  # Cache for 5 minutes
        
        return permissions

class APIAuthenticationMiddleware:
    """API authentication middleware for token-based auth"""
    
    def __init__(self, get_response):
        self.get_response = get_response
    
    def __call__(self, request):
        # Check for API token in headers
        auth_header = request.META.get('HTTP_AUTHORIZATION', '')
        
        if auth_header.startswith('Bearer '):
            token = auth_header.split(' ')[1]
            user = self.authenticate_token(token)
            
            if user:
                request.user = user
            else:
                request.user = AnonymousUser()
        
        response = self.get_response(request)
        return response
    
    def authenticate_token(self, token):
        """Authenticate user by token"""
        
        try:
            from myapp.models import APIToken
            
            api_token = APIToken.objects.select_related('user').get(
                token=token,
                is_active=True,
                expires_at__gt=timezone.now()
            )
            
            return api_token.user
        
        except APIToken.DoesNotExist:
            return None

# Middleware configuration in settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',  # Required
    'myapp.middleware.CustomAuthenticationMiddleware',          # Custom enhancement
    'myapp.middleware.APIAuthenticationMiddleware',             # API auth
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

Django's authentication system provides a comprehensive foundation for managing user identity and access control. Understanding its architecture and components enables you to build secure, scalable authentication solutions that meet your application's specific requirements.