March 22, 2023
User Management and Authentication in Django
March 22, 2023
In the digital age, security is a primary concern for any web application. A key part of that security is ensuring that users are who they claim to be, and that they can only access the parts of the application that they're authorized to. This is where user authentication and authorization come in.
Authentication is the process of verifying a user's identity, usually through a username and password combination. Authorization, on the other hand, determines what a user is allowed to do and which resources they can access once they are authenticated.
Django provides a robust system for managing both authentication and authorization. Its built-in capabilities allow developers to create secure applications without having to reinvent the wheel.
In this blog post, we will delve into Django's authentication and authorization system, exploring how to register users, authenticate them, manage passwords, authorize users, and even customize these processes to suit the unique needs of your application.
Understanding Authentication and Authorization
Before diving into the technical aspects, it's important to clearly distinguish between two key terms: authentication and authorization. These two concepts are fundamental to understanding the security of web applications.
Authentication
is the process of verifying the identity of a user. When users provide their username (or email) and password, the system compares this information with the stored data. If there's a match, the system confirms that the users are indeed who they claim to be. In other words, authentication answers the question, "Who is the user?"
Authorization
, on the other hand, follows authentication and determines the privileges a user has within a system. Once users are authenticated, the system knows who they are. The next step is to determine what they are allowed to do. Authorization answers the question, "What is the user allowed to do?" It involves permissions and roles that control access to resources and operations.
It's essential to note that authentication always precedes authorization. A system must first know who a user is before it can assign them the correct permissions.
In the context of Django, it provides a built-in system for both authentication and authorization, which we will explore in detail in the following sections.
Django Authentication System
The Django framework comes with a robust built-in authentication system that takes care of user management, session management, and even includes tools for common tasks like password resets and form handling for login and logout.
The authentication system in Django revolves around the User model, which is a built-in model (database table) to store user-related information. It contains fields such as username, password, email, first_name, last_name, and a few others. Passwords are stored as hashed values for added security.
Django’s authentication system provides the following capabilities:
User registration
: Users can create an account in your application.
User login
: Users can log in to their account using their credentials.
User logout
: Users can safely log out from their account.
Password management
: Django handles password hashing and comparison automatically. It also provides tools to handle password resets.
Session management
: Django manages user sessions, allowing you to keep track of user data between requests.
Group and permissions management
: Django allows you to define groups of permissions and assign them to users.
The authentication system is included by default when you create a new Django project. You can find it in INSTALLED_APPS
in your settings file as 'django.contrib.auth'
.
In the following sections, we'll go in-depth on how to utilize these features in your Django application.
User Registration in Django
User registration is the process where a new user provides necessary information, such as username and password, to create a new account in the application. Django provides tools to make this process straightforward.
Let's create a simple registration form using Django's built-in UserCreationForm
. This form includes fields for the username and password.
# in your views.py
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render, redirect
def register(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
form.save()
return redirect('login') # redirect to login page after successful registration
else:
form = UserCreationForm()
return render(request, 'register.html', {'form': form})
In the code above, a new form instance is created when the request method is GET
. When the request method is POST
(when the form is submitted), the form is initialized with request.POST
(the submitted form data). The form is then checked with form.is_valid()
. If the form is valid, it is saved, which creates a new user, and then the user is redirected to the login page.
Here's how to implement the corresponding HTML form in the register.html
template using Bootstrap classes for styling:
{% extends 'base_generic.html' %}
{% block content %}
<h2>Register</h2>
<form method="post" class="form-group">
{% csrf_token %}
{% for field in form %}
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
{{ field|add_class:"form-control" }}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
{% for error in field.errors %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endfor %}
{% endfor %}
<button type="submit" class="btn btn-primary mt-3">Register</button>
</form>
{% endblock %}
In this template, we use Bootstrap's form-group
, form-label
, form-control
, form-text
, alert, and btn classes to style the form and its components. The add_class
template filter is used to add form-control
class to form fields
After creating the view and template, don't forget to map the view to a URL in your urls.py
file.
# in your urls.py
from django.urls import path
from .views import register
urlpatterns = [
path('register/', register, name='register'),
]
This basic registration process can be customized further as per your needs, which we will discuss in the advanced topics.
User Authentication in Django
Once users have registered, they need a way to log in to the application. Django provides an authentication method to verify a user's credentials and log them in.
Let's create a simple login view using Django's built-in AuthenticationForm
. This form includes fields for the username and password.
# in your views.py
from django.contrib.auth import authenticate, login
from django.contrib.auth.forms import AuthenticationForm
from django.shortcuts import render, redirect
def login_view(request):
if request.method == 'POST':
form = AuthenticationForm(request, data=request.POST)
if form.is_valid():
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
return redirect('home') # redirect to home page after successful login
else:
form = AuthenticationForm()
return render(request, 'login.html', {'form': form})
In the login_view
function, we create an instance of AuthenticationForm
when the request method is POST, and validate the form using form.is_valid()
. If the form is valid, we authenticate the user with the authenticate() function, which checks the user's credentials. If the user exists and the password is correct, authenticate()
returns a User object. We then log in the user using the login()
function.
The corresponding HTML form in the login.html
template could look like this:
{% extends 'base_generic.html' %}
{% block content %}
<h2>Login</h2>
<form method="post" class="form-group">
{% csrf_token %}
{% for field in form %}
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
{{ field|add_class:"form-control" }}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
{% for error in field.errors %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endfor %}
{% endfor %}
<button type="submit" class="btn btn-primary mt-3">Login</button>
</form>
{% endblock %}
After creating the view and template, map the view to a URL in your urls.py
file.
# in your urls.py
from django.urls import path
from .views import login_view
urlpatterns = [
path('login/', login_view, name='login'),
]
To log out a user, Django provides a logout function. Here's a simple logout view:
# in your views.py
from django.contrib.auth import logout
from django.shortcuts import redirect
def logout_view(request):
logout(request)
return redirect('home') # or 'login'
And the corresponding URL mapping:
# in your urls.py
from django.urls import path
from .views import logout_view
urlpatterns = [
path('logout/', logout_view, name='logout'),
]
By calling the logout(request) function, Django will log out the user associated with the current session.
Password Management in Django
Managing user passwords securely is a crucial part of user authentication. Django provides built-in tools for password hashing, changing, and resetting, which can significantly simplify this process.
When a user is created using Django's UserCreationForm
, the password is automatically hashed using the password hashing algorithm specified in the project settings. By default, Django uses the PBKDF2 algorithm with a SHA256 hash, which is currently considered secure. The hashed password is then stored in the database, and the original plain-text password is discarded.
When a user tries to log in, the password they provide is hashed using the same algorithm, and the resulting hash is compared to the stored hash. If the hashes match, the password is correct, and the user is authenticated.
Changing Passwords
Django provides a built-in form, PasswordChangeForm
, which can be used to change a user's password. This form requires the user to enter their old password and the new password twice.
Here's how you can create a view to handle password change:
# in your views.py
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import PasswordChangeForm
from django.shortcuts import render, redirect
def change_password(request):
if request.method == 'POST':
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
user = form.save()
update_session_auth_hash(request, user) # Important!
return redirect('change_password_done')
else:
form = PasswordChangeForm(request.user)
return render(request, 'change_password.html', {'form': form})
The update_session_auth_hash()
function is used to keep the user logged in after they have changed their password.
The change_password.html
template might look like this:
{% extends 'base_generic.html' %}
{% block content %}
<h2>Change Password</h2>
<form method="post" class="form-group">
{% csrf_token %}
{% for field in form %}
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
{{ field|add_class:"form-control" }}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
{% for error in field.errors %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endfor %}
{% endfor %}
<button type="submit" class="btn btn-primary mt-3">Change Password</button>
</form>
{% endblock %}
Resetting Passwords
Django also provides a set of views for password reset. These views handle the process of sending the password reset email, generating a one-time use token, and resetting the password.
To enable password reset functionality, you need to include Django's auth.urls
in your project's URL configuration:
# in your urls.py
from django.urls import path, include
urlpatterns = [
path('accounts/', include('django.contrib.auth.urls')), # add this
]
This will include several views, including password_reset, password_reset_done, password_reset_confirm, and password_reset_complete. You need to create templates for each of these views (password_reset_form.html, password_reset_done.html, password_reset_confirm.html, and password_reset_complete.html).
User Authorization in Django
Once users are authenticated, you need to determine what they are allowed to do. This process is called authorization. Django provides a flexible and powerful permission and authorization system out of the box.
Permissions
In Django, a permission is a specific action that a user can be allowed to perform. Django automatically creates permissions for each model, for add, change, and delete operations. You can also define your own custom permissions on a per-model basis.
You can check if a user has a specific permission using the user.has_perm('app_label.permission')
method. For example, to check if a user has permission to add a book, you can do:
if request.user.has_perm('library.add_book'):
# The user can add a book
else:
# The user cannot add a book
User Groups
For easier management of permissions, you can create groups of users. You can assign permissions to a group, and all users in that group will have those permissions. You can create and manage groups through the Django admin site.
Checking if a User is Authenticated
In your views, you can check if a user is authenticated using the user.is_authenticated
property. This can be used to restrict access to views to logged-in users. For example:
def my_view(request):
if request.user.is_authenticated:
# The user is logged in
else:
# The user is not logged in
The login_required Decorator
If you want to restrict a view to only logged-in users, you can use the login_required
decorator:
from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):
# This view is only accessible to logged-in users
If a non-authenticated user tries to access this view, they will be redirected to the login page.
The UserPassesTestMixin
If you need more complex authorization checks, you can use Django's class-based views and the UserPassesTestMixin
. This mixin allows you to define a test function that a user must pass to access the view. For example:
from django.contrib.auth.mixins import UserPassesTestMixin
from django.views.generic import TemplateView
class MyView(UserPassesTestMixin, TemplateView):
template_name = 'my_template.html'
def test_func(self):
return self.request.user.username == 'admin'
In this example, only the user with the username 'admin' can access the view.
Remember, Django’s permission and authorization system is designed to give you as much control as you need, while making common cases straightforward.
Advanced Topics: Django's User Model
The User model is one of Django's built-in models, designed to handle common user-related tasks. It includes fields like username, password
, email
, first_name
, last_name, and more. However, there might be situations where you want to store additional information about users.
Django allows you to extend the User model in several ways:
Extending the User model using a One-To-One link
One way to extend the User model is to create a new model with a one-to-one link to the User model. This is useful if you want to store additional information about users, but don't need to change Django's built-in authentication behavior.
# in your models.py
from django.contrib.auth.models import User
from django.db import models
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(blank=True)
location = models.CharField(max_length=30, blank=True)
In this example, we've added a bio
and location
field to each user. You can access these fields through the user.profile
attribute.
Substituting the User model
If you need to change more fundamental behavior of the User model, such as what fields are used for authentication, you can completely replace the User model with your own model.
To do this, you'll need to create a model that includes all the necessary fields and methods for authentication, and set the AUTH_USER_MODEL
setting in your project's settings to point to your custom model.
# in your models.py
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
bio = models.TextField(blank=True)
location = models.CharField(max_length=30, blank=True)
# in your settings.py
AUTH_USER_MODEL = 'myapp.CustomUser'
In this example, we've created a custom user model that includes bio
and location
fields. The AbstractUser
base class includes the core implementation of a User
model, including hashed password and token methods.
Note
: If you're starting a new project, it's easier to set up a custom user model at the beginning. Changing the user model mid-project can be more complex because you'll need to migrate your database to the new model.
Advanced Topics: Custom Authentication and Authorization
There might be situations where Django's built-in authentication and authorization systems are not sufficient for your needs. In such cases, you can create custom authentication backends and permissions.
Custom Authentication Backends
In Django, an authentication backend is a class that authenticates a set of credentials. Django comes with a built-in authentication backend that authenticates users based on their username and password, but you can create your own authentication backend if you need to authenticate users in a different way.
To create a custom authentication backend, you'll need to create a class that includes a get_user(user_id)
method and an authenticate(request, **credentials)
method. Then, add your custom authentication backend to the AUTHENTICATION_BACKENDS
setting in your project's settings.
Here is an example of a custom authentication backend:
# in your backends.py
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
class EmailBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
UserModel = get_user_model()
try:
user = UserModel.objects.get(email=username)
except UserModel.DoesNotExist:
return None
if user.check_password(password):
return user
def get_user(self, user_id):
UserModel = get_user_model()
try:
return UserModel.objects.get(pk=user_id)
except UserModel.DoesNotExist:
return None
# in your settings.py
AUTHENTICATION_BACKENDS = ['path.to.your.EmailBackend', 'django.contrib.auth.backends.ModelBackend']
In this example, we've created a custom authentication backend that authenticates users based on their email and password, rather than their username and password.
Custom Permissions
In Django, a permission is a boolean value that specifies whether a user can perform a certain action. By default, Django provides permissions to add, change, and delete instances of a model, but you can create your own custom permissions if you need to.
To create a custom permission, you'll need to add a permissions
option to the Meta
class of your model. Then, you can check for the permission in your views using the user.has_perm()
method.
Here is an example of a custom permission:
# in your models.py
class Book(models.Model):
# fields...
class Meta:
permissions = [
("can_edit_publish_date", "Can edit publish date"),
]
# in your views.py
def edit_publish_date(request, book_id):
book = get_object_or_404(Book, id=book_id)
if request.user.has_perm('library.can_edit_publish_date'):
# The user can edit the publish date
else:
# The user cannot edit the publish date
In this example, we've created a custom permission that allows a user to edit the publish date of a book.
Conclusion
In this article, we've explored Django's powerful user management features, including its built-in authentication and authorization systems, the User model, and even how to create custom authentication backends and permissions.
Authentication and authorization are crucial aspects of web development, ensuring that users are who they say they are, and that they only have access to the areas and functionality of your site that you intend. Thankfully, Django provides a robust system to handle these functionalities, allowing you to focus more on your application's unique features rather than the specifics of secure user management.
In Django, the process of registering, authenticating, and managing users has been abstracted in a way that's consistent and straightforward to use, but flexible enough to cater to more advanced or specific use cases.
However, remember that security is a vast topic. This post has covered the basics and should serve as a solid foundation, but be sure to continue researching best practices and Django's documentation as you go about
implementing your own user management system.
Remember that user management is a crucial aspect of your website or application and should be approached with care. Make use of Django's existing tools, but also don't be afraid to extend them to meet your specific needs.