Authentication In Django

Outline

  • Authentication
  • Protect With Login

Model

  • django.contrib.auth.models.User

Features

  • Login
  • Logout
  • Change Password
  • Change Reset
  • Permission
  • Group

Views And Urls

  • LoginView
  • LogoutView
  • PasswordChangeView
  • PasswordResetView

Forms

  • AuthenticationForm: Used for login
  • UserCreationForm: Used for creating a new user
  • PasswordChangeForm: Used for changing the password
  • PasswordResetForm: Used for password reset requests

Middleware

  • AuthenticationMiddleware: Associates users with requests using sessions.

Auth Backends

Django supports multiple authentication backends, allowing you to authenticate against various sources.

ModelBackend

Step 1: Install Django and Start a Project

pip install django
django-admin startproject myproject
cd myproject
python manage.py startapp myapp

Step 2: Update settings.py

Add django.contrib.auth and your app (myapp) to INSTALLED_APPS.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp',
]

Step 3.1: Create User Authentication Views in views.py

from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render, redirect

def signup(request):
    if request.method == 'POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('login')
    else:
        form = UserCreationForm()
    return render(request, 'signup.html', {'form': form})

Step 3.2: Create User Authentication Views in views.py

def user_login(request):
    if request.method == 'POST':
        username = request.POST['username']
        password = request.POST['password']
        user = authenticate(request, username=username, password=password)
        if user is not None:
            login(request, user)
            return redirect('home')
        else:
            return render(request, 'login.html', {'error': 'Invalid credentials'})
    return render(request, 'login.html')

Step 3.3: Create User Authentication Views in views.py

def user_logout(request):
	logout(request)
	return redirect('login')

Step 3.4: Create User Authentication Views in views.py

from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render, redirect

def signup(request):
    if request.method == 'POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('login')
    else:
        form = UserCreationForm()
    return render(request, 'signup.html', {'form': form})

def user_login(request):
    if request.method == 'POST':
        username = request.POST['username']
        password = request.POST['password']
        user = authenticate(request, username=username, password=password)
        if user is not None:
            login(request, user)
            return redirect('home')
        else:
            return render(request, 'login.html', {'error': 'Invalid credentials'})
    return render(request, 'login.html')

def user_logout(request):
    logout(request)
    return redirect('login')

Step 4: Define URLs in urls.py

from django.contrib import admin
from django.urls import path
from myapp import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('signup/', views.signup, name='signup'),
    path('login/', views.user_login, name='login'),
    path('logout/', views.user_logout, name='logout'),
    path('', views.home, name='home'),  # Define a home view in views.py
]

Step 5: Create Templates

Create HTML templates for signup.html, login.html, and other views in a templates directory inside your app.

<!DOCTYPE html>
<html>
<head>
    <title>Sign Up</title>
</head>
<body>
    <h2>Sign Up</h2>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">Sign Up</button>
    </form>
</body>
</html>

Step 5: Create Templates

login.html

<!DOCTYPE html>
<html>
<head>
    <title>Login</title>
</head>
<body>
    <h2>Login</h2>
    {% if error %}
        <p style="color: red;">{{ error }}</p>
    {% endif %}
    <form method="post">
        {% csrf_token %}
        <label for="username">Username:</label>
        <input type="text" id="username" name="username">
        <label for="password">Password:</label>
        <input type="password" id="password" name="password">
        <button type="submit">Login</button>
    </form>
</body>
</html>

7. Using Built-in Authentication Views

Django provides built-in views for login, logout, and password management which you can use directly. For instance:

from django.urls import path
from django.contrib.auth import views as auth_views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('signup/', views.signup, name='signup'),
    path('login/', auth_views.LoginView.as_view(template_name='login.html'), name='login'),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
    path('password_change/', auth_views.PasswordChangeView.as_view(), name='password_change'),
    path('password_change/done/', auth_views.PasswordChangeDoneView.as_view(), name='password_change_done'),
    path('password_reset/', auth_views.PasswordResetView.as_view(), name='password_reset'),
    path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'),
    path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
    path('reset/done/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
]

Protect With Login - Decorator

1. Using the login_required Decorator

The login_required decorator is the most straightforward way to protect a view. You simply add it above your view function. If a user is not authenticated, they will be redirected to the login page.

from django.contrib.auth.decorators import login_required
from django.shortcuts import render

@login_required
def protected_view(request):
    return render(request, 'protected.html')

in `settings.py`: Ensure that you have a login URL specified so that the decorator knows where to redirect unauthenticated users.

LOGIN_URL = '/login/'

2. Using Class-Based Views

For class-based views, you can use the LoginRequiredMixin provided by Django.

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView

class ProtectedView(LoginRequiredMixin, TemplateView):
    template_name = 'protected.html'

3. Using URL Configuration

You can also protect an entire set of URLs by using django.contrib.auth.decorators.login_required in your URL configuration.

from django.urls import path
from django.contrib.auth.decorators import login_required
from myapp import views

urlpatterns = [
    path('protected/', login_required(views.protected_view), name='protected'),
]

permissions and groups

In Django, permissions and groups are a key part of the authentication framework. They allow you to define what actions users can perform and to manage user roles more efficiently. Here's a detailed explanation:

Permissions

Permissions in Django are used to define what actions a user can perform. By default, Django provides three permissions for each model:

  • add_modelname
  • change_modelname
  • delete_modelname

These permissions are created when you run makemigrations and migrate.

Custom Permissions

You can also define custom permissions in your models.

from django.db import models

class MyModel(models.Model):
    name = models.CharField(max_length=100)

    class Meta:
        permissions = [
            ('can_publish', 'Can Publish Posts'),
        ]

Checking Permissions

You can check permissions in your views or templates.

from django.shortcuts import render
from django.contrib.auth.decorators import permission_required

@permission_required('mymodel.can_publish')
def my_view(request):
    return render(request, 'my_template.html')

Groups

Groups in Django allow you to categorize users and assign permissions to these groups, which can then be inherited by the users in the group. This is useful for managing roles within an application.

Creating and Assigning Groups Using Admin Interface

You can create and assign groups either through the Django admin interface or programmatically.

  • Go to the Django admin.
  • Navigate to the "Groups" section.
  • Create a new group and assign the necessary permissions.

Creating and Assigning Groups Using Programmatically

`management/commands/assign_groups.py`

from django.core.management.base import BaseCommand
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from myapp.models import MyModel

class Command(BaseCommand):
    def handle(self, *args, **options):
        # Create a group
        group, created = Group.objects.get_or_create(name='Editors')

        # Add permissions to group
        content_type = ContentType.objects.get_for_model(MyModel)
        permissions = Permission.objects.filter(content_type=content_type)
        for permission in permissions:
            group.permissions.add(permission)

        # Assign group to a user
        from django.contrib.auth.models import User
        user = User.objects.get(username='john')
        user.groups.add(group)
        user.save()

Using Permissions and Groups in Views

You can use decorators or mixins to enforce permissions in your views.

Using Permissions and Groups in Function Based Views

from django.shortcuts import render
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.decorators import login_required

@login_required
@permission_required('mymodel.can_publish', raise_exception=True)
def my_view(request):
    return render(request, 'my_template.html')

Using Permissions and Groups in Class Based Views

For class-based views, use the `PermissionRequiredMixin`.

from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import TemplateView

class MyView(PermissionRequiredMixin, TemplateView):
    template_name = 'my_template.html'
    permission_required = 'mymodel.can_publish'

Permissions and Groups in Templates

You can also check for permissions and group memberships in your templates.

{% if user.has_perm('mymodel.can_publish') %}
    

You have permission to publish posts.

{% else %}

You do not have permission to publish posts.

{% endif %} {% if user.groups.filter(name='Editors').exists %}

You are an editor.

{% else %}

You are not an editor.

{% endif %}

Summary

  1. `Permissions`: Define what actions a user can perform. Built-in permissions are provided for each model (add, change, delete), and you can define custom permissions.
  2. `Groups`: Categorize users into roles and assign permissions to these roles. Users inherit permissions from the groups they belong to.
  3. `Usage`: Check permissions and group memberships in views, either via decorators/mixins or directly in templates.

Authentication in DRF

Basic Authentication

Basic authentication uses the HTTPBasicAuthentication class, where the client sends the username and password encoded in the HTTP headers.

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.BasicAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

Token Authentication

Token authentication uses the TokenAuthentication class. Each user is assigned a unique token, which they must include in the Authorization header of their requests.

pip install djangorestframework drf-yasg
pip install djangorestframework-authtoken

Add rest_framework and rest_framework.authtoken to your INSTALLED_APPS

INSTALLED_APPS = [
    ...
    'rest_framework',
    'rest_framework.authtoken',
    ...
]

Run the migrations

python manage.py migrate

Update settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

Create a view to generate tokens

from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response

class CustomAuthToken(ObtainAuthToken):
    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data,
                                           context={'request': request})
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        token, created = Token.objects.get_or_create(user=user)
        return Response({
            'token': token.key,
            'user_id': user.pk,
            'email': user.email
        })

Update urls.py

from django.urls import path
from myapp.views import CustomAuthToken

urlpatterns = [
    path('api-token-auth/', CustomAuthToken.as_view()),
]

3. Session Authentication

Session authentication uses Django's session framework. It’s suitable for web applications where the client and server share the same session

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

4. JWT Authentication

JWT (JSON Web Token) authentication is commonly used for API authentication. It provides a compact and self-contained way to securely transmit information between parties as a JSON object.

pip install djangorestframework-simplejwt
from datetime import timedelta

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    'ROTATE_REFRESH_TOKENS': False,
    'BLACKLIST_AFTER_ROTATION': True,
    'ALGORITHM': 'HS256',
    'SIGNING_KEY': SECRET_KEY,
    'VERIFYING_KEY': None,
    'AUTH_HEADER_TYPES': ('Bearer',),
}

Update urls.py

from django.urls import path
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]

5. Custom Authentication

You can create custom authentication classes by extending the BaseAuthentication class. in authentication.py

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from django.contrib.auth.models import User

class CustomAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.META.get('HTTP_AUTHORIZATION')
        if not token:
            return None

        try:
            user = User.objects.get(auth_token=token)
        except User.DoesNotExist:
            raise AuthenticationFailed('Invalid token')

        return (user, None)
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'myapp.authentication.CustomAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

Protecting Views with Permissions

You can use the IsAuthenticated permission class to ensure that only authenticated users can access certain views. You can also create custom permission classes.

Example of Using Built-in Permissions in `views.py`

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated

class ProtectedView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        content = {'message': 'This is a protected view'}
        return Response(content)

Example of a Custom Permission Class in `permissions.py`

from rest_framework.permissions import BasePermission

class IsAdminUser(BasePermission):
    def has_permission(self, request, view):
        return request.user and request.user.is_staff

views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from myapp.permissions import IsAdminUser

class AdminView(APIView):
    permission_classes = [IsAdminUser]

    def get(self, request):
        content = {'message': 'This is an admin view'}
        return Response(content)

Summary

By configuring these authentication methods, you can secure your Django REST Framework API and ensure that only authorized users can access or modify resources.

  1. Basic Authentication: Uses HTTP basic authentication.
  2. Token Authentication: Uses token-based authentication, where each user has a unique token.
  3. Session Authentication: Uses Django's session framework.
  4. JWT Authentication: Uses JSON Web Tokens for stateless authentication.
  5. Custom Authentication: Create custom authentication logic by extending the BaseAuthentication class.