Django Introduction Part9 - User authentication and permissions
Django provides an authentication and authorization (“permission”) system that allows you to verify user credentials and define what actions each user is allowed to perform. The framework includes built-in models for Users and Groups (a generic way of applying permissions to more than one user at a time), permissions/flags that designate whether a user may perform a task, forms and views for logging in users, and view tools for restricting content.
The authentication system is very flexible, and you can build up your URLs, forms, views, and templates from scratch if you like, just calling the provided API to log in the user.
Enabling authentication
The authentication was enabled automatically so you don’t need to do anything more at this point.
Note: The necessary configuration was all done for us when we created the app using the django-admin startproject command. The database tables for users and model permissions were created when we first called python manage.py migrate.
The configuration is set up in the INSTALLED_APPS and MIDDLEWARE sections of the project file, as shown below:
1 | INSTALLED_APPS = [ |
Creating users and groups
Coz our superuser is already authenticated and has all permissions, so we’ll need to create a test user to represent a normal site user.
Note: You can also create users programmatically, as shown below. You would have to do this, for example, if developing an interface to allow users to create their own logins (you shouldn’t give users access to the admin site).
1 | from django.contrib.auth.models import User |
Below we’ll first create a group and then a user. Even though we don’t have any permissions to add for our library members yet, if we need to later, it will be much easier to add them once to the group than individually to each member.
Start the development server and navigate to the admin site in your local web browser (http://127.0.0.1:8000/admin/). Login to the site using the credentials for your superuser account. The top level of the Admin site displays all of your models, sorted by “Django application”. From the Authentication and Authorisation section, you can click the Users or Groups links to see their existing records.
Setting up your authentication views
Django provides almost everything you need to create authentication pages to handle login, log out, and password management “out of the box”. This includes a URL mapper, views and forms, but it does not include the templates — we have to create our own!
Add the following to the bottom of the project urls.py file:
1 | #Add Django site authentication urls (for login, logout, password management) |
Navigate to the http://127.0.0.1:8000/accounts/ URL (note the trailing forward slash!) and Django will show an error that it could not find this URL, and listing all the URLs it tried. From this you can see the URLs that will work, for example:
Note: Using the above method adds the following URLs with names in square brackets, which can be used to reverse the URL mappings. You don’t have to implement anything else — the above URL mapping automatically maps the below mentioned URLs.
1 | accounts/ login/ [name='login'] |
Now try to navigate to the login URL (http://127.0.0.1:8000/accounts/login/). This will fail again, but with an error that tells you that we’re missing the required template (registration/login.html) on the template search path. You’ll see the following lines listed in the yellow section up the top:
1 | Exception Type: TemplateDoesNotExist |
The next step is to create a registration directory on the search path and then add the login.html file.
Template directory
The URLs (and implicitly views) that we just added expect to find their associated templates in a directory /registration/ somewhere in the templates search path.
For this site, we’ll put our HTML pages in the templates/registration/ project directory.
To make these directories visible to the template loader (i.e. to put this directory in the template search path) open the project settings, and update the TEMPLATES section’s 'DIRS' line as shown.
1 | TEMPLATES = [ |
Login template
Important: The authentication templates provided in this article are a very basic/slightly modified version of the Django demonstration login templates. You may need to customise them for your own use!
Create a new templates/registration/login.html file and give it the following contents:
1 | {% extends "movie_base.html" %} |
When the user is not logged in,
request.user/useralso exists, which is an instance of theAnonymousUserclass. The best way to determine whether the current user is logged in is to determine theis_authenticatedread-only attribute of the User object.
If you try to log in that will succeed and you’ll be redirected to another page (by default this will be http://127.0.0.1:8000/accounts/profile/). The problem here is that, by default, Django expects that after login you will want to be taken to a profile page, which may or may not be the case. As you haven’t defined this page yet, you’ll get another error!
Open the project settings and add the text below to the bottom. Now when you log in you should be redirected to the site homepage by default.
1 | # Redirect to home URL after login (Default redirects to /accounts/profile/) |
Logout template
If you navigate to the logout URL (http://127.0.0.1:8000/accounts/logout/) then you’ll see some odd behaviour — your user will be logged out sure enough, but you’ll be taken to the Admin logout page. That’s not what you want, if only because the login link on that page takes you to the Admin login screen (and that is only available to users who have the is_staff permission).
Create and open /templates/registration/logged_out.html. Copy in the text below:
1 | {% extends "movie_base.html" %} |
This template is very simple. It just displays a message informing you that you have been logged out, and provides a link that you can press to go back to the login screen.
Password reset templates
The default password reset system uses email to send the user a reset link. You need to create forms to get the user’s email address, send the email, allow them to enter a new password, and to note when the whole process is complete.
The following templates can be used as a starting point.
Password reset form
This is the form used to get the user’s email address (for sending the password reset email). Create /templates/registration/password_reset_form.html, and give it the following contents:
1 | {% extends "movie_base.html" %} |
Password reset done
This form is displayed after your email address has been collected. Create /templates/registration/password_reset_done.html, and give it the following contents:
1 | {% extends "movie_base.html" %} |
Note: if the email address not exists,
password_reset_confirmwill not triger.
Password reset email
This template provides the text of the HTML email containing the reset link that we will send to users. Create /templates/registration/password_reset_email.html, and give it the following contents:
1 | Someone asked for password reset for email {{ email }}. Follow the link below: |
The link from development would be like this:
http://127.0.0.1:8000/users/reset/MQ/acm8o3-a3df5d8d942f66d87b67ee393194d7f0/
Password reset confirm
This page is where you enter your new password after clicking the link in the password reset email. Create /templates/registration/password_reset_confirm.html, and give it the following contents:
1 | {% extends "movie_base.html" %} |
Password reset complete
This is the last password-reset template, which is displayed to notify you when the password reset has succeeded. Create /templates/registration/password_reset_complete.html, and give it the following contents:
1 | {% extends "movie_base.html" %} |
Testing the new authentication pages
Now that you’ve added the URL configuration and created all these templates, the authentication pages should now just work!
You can test the new authentication pages by attempting to log in and then log out your superuser account using these URLs:
You’ll be able to test the password reset functionality from the link in the login page. Be aware that Django will only send reset emails to addresses (users) that are already stored in its database!
Note: The password reset system requires that your website supports email, which is beyond the scope of this article, so this part won’t work yet. To allow testing, put the following line at the end of your settings.py file. This logs any emails sent to the console (so you can copy the password reset link from the console).
1 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' |
For more information, see https://docs.djangoproject.com/en/3.1/topics/email/ (Django docs).
Testing against authenticated users
This section looks at what we can do to selectively control content the user sees based on whether they are logged in or not.
Testing in templates
You can get information about the currently logged in user in templates with the {{ user }}template variable (this is added to the template context by default when you set up the project as we did in our skeleton).
Typically you will first test against the {{ user.is_authenticated }} template variable to determine whether the user is eligible to see specific content. To demonstrate this, next we’ll update our sidebar to display a “Login” link if the user is logged out, and a “Logout” link if they are logged in.
1 | <ul class="sidebar-nav"> |
As you can see, we use if-else-endif template tags to conditionally display text based on whether {{ user.is_authenticated }} is true. If the user is authenticated then we know that we have a valid user, so we call {{ user.get_username }} to display their name.
We create the login and logout link URLs using the url template tag and the names of the respective URL configurations.
Note also how we have appended ?next={{request.path}}to the end of the URLs. What this does is add a URL parameter next containing the address (URL) of the current page, to the end of the linked URL. After the user has successfully logged in/out, the views will use this “next” value to redirect the user back to the page where they first clicked the login/logout link.
Testing in views
If you’re using function-based views, the easiest way to restrict access to your functions is to apply the login_required decorator to your view function, as shown below. If the user is logged in then your view code will execute as normal.
If the user is not logged in, this will redirect to the login URL defined in the project settings (settings.LOGIN_URL), passing the current absolute path as the next URL parameter.
If the user succeeds in logging in then they will be returned back to this page, but this time authenticated.
1 | from django.contrib.auth.decorators import login_required |
Note: You can do the same sort of thing manually by testing on request.user.is_authenticated, but the decorator is much more convenient!
Similarly, the easiest way to restrict access to logged-in users in your class-based views is to derive from LoginRequiredMixin. You need to declare this mixin first in the superclass list, before the main view class.
1 | from django.contrib.auth.mixins import LoginRequiredMixin |
This has exactly the same redirect behaviour as the login_required decorator. You can also specify an alternative location to redirect the user to if they are not authenticated (login_url), and a URL parameter name instead of “next” to insert the current absolute path (redirect_field_name).
1 | class MyView(LoginRequiredMixin, View): |
Example — listing the current user’s ratings
Now that we know how to restrict a page to a particular user, let’s create a view of the movies that the current user has watched.
Models
First, we’re going to create one using a ForeignKey field. We also need an easy mechanism to test whether a loaned book is overdue.
Open movie/models.py, and import the User model from django.contrib.auth.models:
1 | from django.contrib.auth.models import User |
Next, add the user field to the Rating model:
1 | user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) |
1 |
|
Note: We first verify whether movie_release_date is empty before making a comparison. An empty movie_release_date field would cause Django to throw an error instead of showing the page: empty values are not comparable.
View
Now we’ll add a view for getting the list of all movies that have been watched to the current user.
Add the following to movie/views.py:
1 | from django.contrib.auth.mixins import LoginRequiredMixin |
Now open /movie/urls.py and add a path() pointing to the above view.
1 | path('mymovies/', views.WatchedMoviesByUserListView.as_view(), name='my-watched') |
Now, all we need to do for this page is add a template. First, create the template file /catalog/templates/catalog/bookinstance_list_borrowed_user.html and give it the following contents:
1 | ... |
Permissions
Permissions are associated with models and define the operations that can be performed on a model instance by a user who has the permission. By default, Django automatically gives add, change, and delete permissions to all models, which allow users with the permissions to perform the associated actions via the admin site. You can define your own permissions to models and grant them to specific users. You can also change the permissions associated with different instances of the same model.
Defining permissions is done on the model “class Meta” section, using the permissionsfield. You can specify as many permissions as you need in a tuple, each permission itself being defined in a nested tuple containing the permission name and permission display value.
1 | class Rating(models.Model): |
We could then assign the permission to a “yourname” group in the Admin site.
You will need to re-run your migrations to update the database appropriately.
The current user’s permissions are stored in a template variable called {{ perms }}. You can check whether the current user has a particular permission using the specific variable name within the associated Django “app” — e.g. {{ perms.catalog.can_mark_returned }} will be True if the user has this permission, and False otherwise. We typically test for the permission using the template {% if %} tag as shown:
1 | {% if perms.movie.can_rating_movie %} |
Permissions can be tested in function view using the permission_required decorator or in a class-based view using the PermissionRequiredMixin. The pattern and behaviour are the same as for login authentication, though of course, you might reasonably have to add multiple permissions.
Function view decorator:
1 | from django.contrib.auth.decorators import permission_required |
A permission-required mixin for class-based views.
1 | from django.contrib.auth.mixins import PermissionRequiredMixin |
Template
1 | {% if perms.movie.can_rating_movie %}- <a href="{% url 'movie:edit_movie_rating' movie.id %}">Modify</a> {% endif %} |






