Django Logging Users In and Out

Django provides built-in URL patterns and view functions for logging users in and out. But before we add them to our project, we will create login and logout system on our own using some utility functions provided by the Django authentication framework.

The authenticate() and login() functions

Django authentication framework (django.contrib.auth) provides authenticate() and login() functions whose job is to authenticate and login users respectively.

The authenticate() function accepts two keyword arguments, username and password and returns an object of type User, if username and password are valid. Otherwise, it returns None.

The authenticate() function only verifies whether the credentials provided are valid or not. It doesn’t login the user.

To login the user we use login() function. It takes two arguments, request object (HttpRequest) and a User object. It works by saving the user’s ID in the session, using Django session framework.

Once a user is logged in, he should be able to logout and this is the responsibility of logout() function.

The logout() function

To logout users we use logout() function. It accepts a request (HttpRequest) object and returns None. Calling logout() function completely deletes the session data and cookie associated with the logged in user.

It is important to note that calling logout() function doesn’t throw any errors if the user is not already logged in.

Now we have enough knowledge to roll out our own login system.

Creating a Login System

In djangobin app’s views.py file , add login, logout and user_details views towards the end of the file as follows:

djangobin/django_project/djangobin/views.py

Then create three templates login.html, logout.html and user_details.html with the following code:

djangobin/django_project/djangobin/templates/djangobin/login.html

djangobin/django_project/djangobin/templates/djangobin/logout.html

djangobin/django_project/djangobin/templates/djangobin/user_details.html

Nothing extraordinary here, we are just using some of the attributes we have learned in the chapter Django Authentication Framework Basics to get some information about the logged in user.

Add links to login and logout page in base.html file as follows:

djangobin/django_project/djangobin/templates/djangobin/base.html

Finally, add the following three URL patterns in the djangobin app’s urls.py file:

djangobin/django_project/djangobin/urls.py

Start the development server and visit http://127.0.0.1:8000/login/. You should get a page like this:

Enter bogus username and password and you will get errors like this:

Now enter correct username and password and you will be redirected to user detail page:

To logout, click the logout link at the right side of the page. And you should see logout page like this:

Using built-in login() and logout() views

Django provides two built-in views django.contrib.auth.login() and django.contrib.auth.logout() to login and logout users respectively.

To use these views, import them from django.contrib.auth package and update login and logout URL patterns in the urls.py file as follows:

djangobin/django_project/djangobin/urls.py

Save the urls.py file and visit http://127.0.0.1:8000/login/. You will get a TemplateDoesNotExist exception as follows:

The problem is that by default, the django.contrib.auth.login() view looks for a template called registration/login.html. However, Django doesn’t provide this template that’s why a TemplateDoesNotExist exception is raised.

Also, notice the Template-loader postmortem section. It tells you the exact order in which Django tried to find the template.

We can pass a different template to the django.contrib.auth.login() view using the template_name keyword argument as follows:

Similarly, by default the django.contrib.auth.logout() view uses registration/logged_out.html template from the admin app (django.contrib.admin). This is the same template which you would see if you logout from the Django admin site.

Visit http://127.0.0.1:8000/logout/ and see it yourself.

Just as with django.contrib.auth.login() view, we can use a different template by passing template_name keyword argument to django.contrib.auth.logout() view as follows:

Modify login and logout URL patterns to use a custom templates as follows:

djangobin/django_project/djangobin/urls.py

Next, update login.html template to use form template variable provided by django.contrib.auth.login() view as follows:

djangobin/django_project/djangobin/templates/djangobin/login.html

Our login view is almost ready. Visit http://127.0.0.1:8000/login/ and try logging in using wrong username and password. You will be greeted with errors like this:

Try logging in one more time using the correct username and password. On success, you will redirect you to /accounts/profile/ URL. This is another default behavior of django.contrib.auth.login() view.

We don’t have any URL pattern in djangobin’s urls.py to match /accounts/profile/ URL path, that’s why the server returned HTTP 404 error.

We can easily override this behavior using the LOGIN_REDIRECT_URL setting. Open settings.py file and add LOGIN_REDIRECT_URL towards the end of the file as follows:

djangobin/django_project/django_project/settings.py

This will change the redirect URL from /accounts/profile/ to /

Instead of passing name of the URL pattern, we can also pass URL path directly.

From now on, after successfull login, django.contrib.auth.login() view will redirect the user to / URL path instead of /accounts/profile/.

But there are still some limitations to this. For example, let’s say you were browsing trending snippets and then you decided to login. After logging in, it makes more sense to redirect you to the trending page again instead of the / URL.

To make this happen, we can embed a hidden field named next containing the URL to redirect to after logging in.

When the django.contrib.auth.login() view receives next as POST data, it redirects to the URL specied in the hidden next field.

The django.contrib.auth.login() view also provides a context variable called next, which contains the URL where useres will be redirected after logging in. The value of the next variable will be either /accounts/profile/ or the URL specified in LOGIN_REDIRECT_URL variable.

We specify the value of the next field using query string like this:

http://127.0.0.1:8000/login/?next=/trending/

Open login.html and add the hidden field named next as follows:

djangobin/django_project/djangobin/templates/djangobin/login.html

This is how the above code works:

If we visit login page using http://localhost:8000/login/ URL, then after logging in django.contrib.auth.login() will redirect the user to / URL. On the other hand, if we visit login page, using http://127.0.0.1:8000/login/?next=/trending/ URL, then the django.contrib.auth.login() view will redirect the user to /trending/ URL.

Next, modify base.html to provide a value to next query parameter as follows:

djangobin/django_project/djangobin/templates/djangobin/base.html

Let’s test whether everything is working or not.

If you are already logged in, logout first by visiting http://localhost:8000/logout/ URL directly or by clicking the Logout link at the top right corner of the page.

Then, navigate to the login page (http://localhost:8000/login/), enter correct username and password. On success, you will be redirected to the index page of djangobin:

Logout again and navigate to the login page again by clicking “Login” link in the trending snippet page. This time after logging in you will be redirected to /trending/ instead of / URL.

Logging in using Email and Password

As you have seen, by default Django requires you to enter username and password to login into the application. If you deliberately want this behavior that’s fine. However, Just to show you how you can take an alternative route, our djangobin application will use email and password to authenticate users. To accomplish this task, we will create a custom form and view function.

Open forms.py and add LoginForm class as follows:

djangobin/django_project/djangobin/forms.py

Next, modify login() view function to use LoginForm as follows:

djangobin/django_project/djangobin/views.py

In line 12, we are checking whether any user associated with the submitted email exists or not.

If the user exists, In line 15, we are authenticating it using the authenticate() function. Notice that the arguments passed to authenticate() function are still username and password.

If the authentication is successful, we login the user using the login() function and redirect it.

Update login and logout URL patterns in urls.py file to use login() and logout function of views.py file as follows:

djangobin/django_project/djangobin/urls.py

Visit login page and enter incorrect email and password. You will get an error like this:

Now, enter correct email and password and you will be redirected to the index page.

Our login and logout system is working as expected, but from the usability point of view, there is still one problem.

The issue is that the login form is still visible to the logged in user.

Displaying login form to a logged in user is absolutely pointless. To fix the issue simply check whether the user is logged in or not at the start of login() view function as follows:

djangobin/django_project/djangobin/views.py

If you now visit http://localhost:8000/login/, after logging in, you will be redirected to the user profile page.

Currently, user profile page just displays the name of the user. We will update it to display a list of snippets in the upcoming lessons.

Limiting Access

The whole point of implementing login system is to prevent unauthorized access to administrative pages.

A simple way to restrict access to pages is to first check whether the user is authenticated or not using the is_authenticated() method and then redirect the user accordingly. For example:

We can copy and paste this condition at the start of the every administrative view function. This will work but Django provides a much better way.

The preferred way to limit access to pages is to use login_required decorator. To use login_required decorator you must import it from django.contrib.auth.decorators module.

Let’s update user_details and logout view to use login_required decorator as follows:

djangobin/django_project/djangobin/views.py

Here is how the login_required decorator work:

If the user is not logged then it will redirect the user to /accounts/login/ (the default login URL), passing the current absolute URL as a value to the next query parameter. On the other hand, if the user is logged in then the login_required would do nothing.

To change default login URL we use LOGIN_URL setting. The LOGIN_URL accepts URL path or name of the URL pattern. Open settings.py file and add the following variable at the end of the file.

djangobin/django_project/django_project/settings.py

This changes the default login from /accounts/login/ to /login/. If you try to visit a view which has login_required decorator applied to it, you will be redirected to /login/ URL instead of /accounts/login/.

To verify the changes visit http://localhost:8000/userdetails/ URL and you will be redirected to http://localhost:8000/login/?next=/userdetails/.

Leave a Comment