Authentication and Authorization

Authentication and Authorization

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

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.

What is Authentication and Authorization?

Authentication and authorization are two fundamental security concepts that work together to protect your application:

  • Authentication: Verifying who a user is (identity verification)
  • Authorization: Determining what an authenticated user is allowed to do (permission checking)

Django's Authentication Framework

# 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)

Core Authentication Components

User Model and Authentication

# 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

Permission System

Understanding Permissions

# 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

Authentication Views and Forms

Built-in Authentication Views

# 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

Security Best Practices

Authentication Security

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.