Django comes with a built-in authentication framework that can handle user authentication, sessions, permissions, and user groups. The authentication system includes views for common user actions such as login, logout, change password, and reset password.
The authentication framework is located at django.contrib.auth
and is used by other Django contrib
packages. Remember that you already used the authentication framework in Chapter 1, Building a Blog Application to create a superuser for your blog application to access the administration site.
When you create a new Django project using the startproject
command, the authentication framework is included in the default settings of your project. It consists of the django.contrib.auth
application and the following two middleware classes found in the MIDDLEWARE_CLASSES
setting of your project:
AuthenticationMiddleware
: Associates users with requests using sessionsSessionMiddleware
: Handles the current session across requestsA middleware is a class with methods that are globally executed during the request or response phase. You will use middleware classes on several occasions throughout this book. You will learn to create custom middlewares in Chapter 13, Going Live.
The authentication framework also includes the following models:
The framework also includes default authentication views and forms that we will use later.
We will start by using the Django authentication framework to allow users to log in into our website. Our view should perform the following actions to log in a user:
First, we are going to create a log in form. Create a new forms.py
file into your account
application directory and add the following lines to it:
from django import forms class LoginForm(forms.Form): username = forms.CharField() password = forms.CharField(widget=forms.PasswordInput)
This form will be used to authenticate users against the database. Note that we use the PasswordInput
widget to render its HTML input
element, including a type="password"
attribute. Edit the views.py
file of your account
application and add the following code to it:
from django.http import HttpResponse from django.shortcuts import render from django.contrib.auth import authenticate, login from .forms import LoginForm def user_login(request): if request.method == 'POST': form = LoginForm(request.POST) if form.is_valid(): cd = form.cleaned_data user = authenticate(username=cd['username'], password=cd['password']) if user is not None: if user.is_active: login(request, user) return HttpResponse('Authenticated ' 'successfully') else: return HttpResponse('Disabled account') else: return HttpResponse('Invalid login') else: form = LoginForm() return render(request, 'account/login.html', {'form': form})
This is what our basic log in view does: when the user_login
view is called with a GET request, we instantiate a new log in form with form = LoginForm()
to display it in the template. When the user submits the form via POST, we perform the following actions:
form = LoginForm(request.POST)
.authenticate()
method. This method takes a username
and a password
and returns a User
object if the user has been successfully authenticated, or None
otherwise. If the user has not been authenticated, we return a raw HttpResponse
displaying a message.is_active
attribute. This is an attribute of Django's User
model. If the user is not active, we return an HttpResponse
displaying the information.login()
method and return a success message.Now, you need to create an URL pattern for this view. Create a new urls.py
file into your account
application directory and add the following code to it:
from django.conf.urls import url from . import views urlpatterns = [ # post views url(r'^login/$', views.user_login, name='login'), ]
Edit the main urls.py
file located in your bookmarks
project directory and include the URL patterns of the account
application as follows:
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^account/', include('account.urls')),
]
The log in view can now be accessed by a URL. It is time to create a template for this view. Since you don't have any templates for this project, you can start by creating a base template that can be extended by the log in template. Create the following files and directories inside the account
application directory:
templates/ account/ login.html base.html
Edit the base.html
file and add the following code to it:
{% load staticfiles %} <!DOCTYPE html> <html> <head> <title>{% block title %}{% endblock %}</title> <link href="{% static "css/base.css" %}" rel="stylesheet"> </head> <body> <div id="header"> <span class="logo">Bookmarks</span> </div> <div id="content"> {% block content %} {% endblock %} </div> </body> </html>
This will be the base template for the website. As we did in our previous project, we include the CSS styles in the main template. You can find these static files in the code that comes along with this chapter. Copy the static/
directory of the account
application to the same location in your project, so that you can use the static files.
The base template defines a title
and a content
block that can be filled with content by the templates that extend from it.
Let's create the template for our log-in form. Open the account/login.html
template and add the following code to it:
{% extends "base.html" %} {% block title %}Log-in{% endblock %} {% block content %} <h1>Log-in</h1> <p>Please, use the following form to log-in:</p> <form action="." method="post"> {{ form.as_p }} {% csrf_token %} <p><input type="submit" value="Log-in"></p> </form> {% endblock %}
This template includes the form that is instantiated in the view. Since our form will be submitted via POST, we include the {% csrf_token %}
template tag for CSRF protection. You learned about CSRF protection in Chapter 2, Enhancing Your Blog with Advanced Features.
There are no users in your database yet. You will need to create a superuser first in order to be able to access the administration site to manage other users. Open the command line and execute python manage.py createsuperuser
. Fill in the desired username, e-mail, and password. Then run the development server using the command python manage.py runserver
and open http://127.0.0.1:8000/admin/
in your browser. Access the administration site using the username and password of the user you just created. You will see the Django administration site, including the User
and Group
models of the Django authentication framework. It will look as follows:
Create a new user using the administration site and open http://127.0.0.1:8000/account/login/
in your browser. You should see the rendered template, including the log-in form:
Now, submit the form leaving one of the fields empty. In this case, you will see that the form is not valid and it displays errors as follows:
If you enter an non-existing user or a wrong password, you will get an Invalid login message.
If you enter valid credentials, you get an Authenticated successfully message like this:
Django includes several forms and views in the authentication framework that you can use straightaway. The login view you have created is a good exercise to understand the process of user authentication in Django. However, you can use the default Django authentication views in most cases.
Django provides the following views to deal with authentication:
login
: Handles a log in form and logs in a userlogout
: Logs out a userlogout_then_login
: Logs out a user and redirects him to the log-in pageDjango provides the following views to handle password changes:
password_change
: Handles a form to change user passwordpassword_change_done
: The success page shown to the user after changing his passwordDjango also includes the following views to allow users to reset their password:
password_reset
: Allows the user to reset his password. It generates a one-time use link with a token and sends it to the user's e-mail account.password_reset_done
: Shows the user that the e-mail to reset his password has been sent to his e-mail account.password_reset_confirm
: Lets the user set a new password.password_reset_complete
: The success page shown to the user after he resets their password.The views listed here can save you a lot of time when creating a website with user accounts. The views use default values that you can override, such as the location of the template to be rendered or the form to be used by the view.
You can get more information about built-in authentication views at https://docs.djangoproject.com/en/1.8/topics/auth/default/#module-django.contrib.auth.views.
Edit the urls.py
of your account application and make it look like this:
from django.conf.urls import url from . import views urlpatterns = [ # previous login view # url(r'^login/$', views.user_login, name='login'), # login / logout urls url(r'^login/$', 'django.contrib.auth.views.login', name='login'), url(r'^logout/$', 'django.contrib.auth.views.logout', name='logout'), url(r'^logout-then-login/$', 'django.contrib.auth.views.logout_then_login', name='logout_then_login'), ]
We comment out the URL pattern for the user_login
view we have created previously to use the login
view of Django's authentication framework.
Create a new directory inside the templates directory of your account
application and name it registration
. This is the default path where the Django authentication views expect your authentication templates to be. Create a new file inside the new directory, name it login.html
, and add the following code to it:
{% extends "base.html" %} {% block title %}Log-in{% endblock %} {% block content %} <h1>Log-in</h1> {% if form.errors %} <p> Your username and password didn't match. Please try again. </p> {% else %} <p>Please, use the following form to log-in:</p> {% endif %} <div class="login-form"> <form action="{% url 'login' %}" method="post"> {{ form.as_p }} {% csrf_token %} <input type="hidden" name="next" value="{{ next }}" /> <p><input type="submit" value="Log-in"></p> </form> </div> {% endblock %}
This login template is quite similar to the one we created before. Django uses the AuthenticationForm
located at django.contrib.auth.forms
by default. This form tries to authenticate the user and raises a validation error if the login was not successful. In this case, we can look for errors using {% if form.errors %}
in the template to check if the provided credentials are wrong. Notice that we have added a hidden HTML <input>
element to submit the value of a variable called next
. This variable is first set by the log in view when you pass a next
parameter in the request (for example. http://127.0.0.1:8000/account/login/?next=/account/
).
The next
parameter has to be a URL. If this parameter is given, the Django login view will redirect to the given URL after the user logs in.
Now, create a logged_out.html
template inside the registration
template directory and make it look like this:
{% extends "base.html" %} {% block title %}Logged out{% endblock %} {% block content %} <h1>Logged out</h1> <p>You have been successfully logged out. You can <a href="{% url "login" %}">log-in again</a>.</p> {% endblock %}
This is the template that Django will display after the user logs out.
After adding the URL patterns and the templates for log in and log out views, your website is ready for users to log in using the Django authentication views.
Now, we are going to create a new view to display a dashboard to the user when he or she logs in their account
. Open the views.py
file of your account application and add the following code to it:
from django.contrib.auth.decorators import login_required @login_required def dashboard(request): return render(request, 'account/dashboard.html', {'section': 'dashboard'})
We decorate our view with the login_required
decorator of the authentication framework. The login_required
decorator checks if the current user is authenticated. If the user is authenticated, it executes the decorated view; if the user is not authenticated, it redirects him to the login URL with the URL he was trying to access as a GET
parameter named next
. By doing so, the log in view redirects the user back to the URL he was trying to access after he is successfully logged in. Remember that we added a hidden input in the form of our log in template for this purpose.
We also define a section
variable. We are going to use this variable to track which section of the site the user is watching. Multiple views may correspond to the same section. This is a simple way to define which section each view corresponds to.
Now, you need to create a template for the dashboard view. Create a new file inside the templates/account/
directory and name it dashboard.html
. Make it look like this:
{% extends "base.html" %} {% block title %}Dashboard{% endblock %} {% block content %} <h1>Dashboard</h1> <p>Welcome to your dashboard.</p> {% endblock %}
Then, add the following URL pattern for this view in the urls.py
file of the account
application:
urlpatterns = [
# ...
url(r'^$', views.dashboard, name='dashboard'),
]
Edit the settings.py
file of your project and add the following code to it:
from django.core.urlresolvers import reverse_lazy LOGIN_REDIRECT_URL = reverse_lazy('dashboard') LOGIN_URL = reverse_lazy('login') LOGOUT_URL = reverse_lazy('logout')
These settings are:
We are using reverse_lazy()
to build the URLs dynamically by their name. The reverse_lazy()
function reverses URLs just like reverse()
does, but you can use it when you need to reverse URLs before your project's URL configuration is loaded.
Let's summarize what you have done so far:
Now, we are going to add log in and log out links to our base template to put everything together.
In order to do this, we have to determine whether the current user is logged in or not, to display the appropriate link in each case. The current user is set in the Http Request
object by the authentication middleware. You can access it with request.user
. You will find a user object in the request even if the user is not authenticated. A non-authenticated user is set in the request as an instance of AnonymousUser
. The best way to check if the current user is authenticated is by calling request.user.is_authenticated()
.
Edit your base.html
and modify the <div>
element with ID header
, like this:
<div id="header"> <span class="logo">Bookmarks</span> {% if request.user.is_authenticated %} <ul class="menu"> <li {% if section == "dashboard" %}class="selected"{% endif %}> <a href="{% url "dashboard" %}">My dashboard</a> </li> <li {% if section == "images" %}class="selected"{% endif %}> <a href="#">Images</a> </li> <li {% if section == "people" %}class="selected"{% endif %}> <a href="#">People</a> </li> </ul> {% endif %} <span class="user"> {% if request.user.is_authenticated %} Hello {{ request.user.first_name }}, <a href="{% url "logout" %}">Logout</a> {% else %} <a href="{% url "login" %}">Log-in</a> {% endif %} </span> </div>
As you can see, we only display the site's menu to authenticated users. We also check the current section to add a selected
class attribute to the corresponding <li>
item in order to highlight the current section in the menu using CSS. We also display the user's first name and a link to log out if the user is authenticated, or a link to log in otherwise.
Now, open http://127.0.0.1:8000/account/login/
in your browser. You should see the log in page. Enter a valid username and password and click the Log-in button. You should see something like this:
You can see that the My dashboard section is highlighted with CSS because it has a selected
class. Since the user is authenticated the first name of the user is displayed in the right side of the header. Click on the Logout link. You should see the following page:
In this page, you can see that the user is logged out, and therefore, you cannot see the menu of the website anymore. The link on the right side of the header is shows now Log-in.
If you are seeing the log out page of the Django administration site instead of your own log out page, check the INSTALLED_APPS
setting of your project and make sure that django.contrib.admin
comes after the account
application. Both templates are located in the same relative path and the Django template loader will use the first one it finds.
We also need our users to be able to change their password after they log in to our site. We are going to integrate Django authentication views for password change. Open the urls.py
file of the account
application and add the following URL patterns to it:
# change password urls url(r'^password-change/$', 'django.contrib.auth.views.password_change', name='password_change'), url(r'^password-change/done/$', 'django.contrib.auth.views.password_change_done', name='password_change_done'),
The password_change
view will handle the form to change the password and the password_change_done
will display a success message after the user has successfully changed his password. Let's create a template for each view.
Add a new file inside the templates/registration/
directory of your account
application and name it password_change_form.html
. Add the following code to it:
{% extends "base.html" %} {% block title %}Change you password{% endblock %} {% block content %} <h1>Change you password</h1> <p>Use the form below to change your password.</p> <form action="." method="post"> {{ form.as_p }} <p><input type="submit" value="Change"></p> {% csrf_token %} </form> {% endblock %}
This template includes the form to change the password. Now, create another file in the same directory and name it password_change_done.html
. Add the following code to it:
{% extends "base.html" %} {% block title %}Password changed{% endblock %} {% block content %} <h1>Password changed</h1> <p>Your password has been successfully changed.</p> {% endblock %}
This template only contains the success message to be displayed when the user has successfully changed their password.
Open http://127.0.0.1:8000/account/password-change/
in your browser. If your user is not logged in, the browser will redirect you to the login page. After you are successfully authenticated, you will see the following change password page:
Fill in the form with your current password and your new password and click the Change button. You will see the following success page:
Log out and log in again using your new password to verify that everything works as expected.
Add the following URL patterns for password restoration to the urls.py
file of the account
application:
# restore password urls url(r'^password-reset/$', 'django.contrib.auth.views.password_reset', name='password_reset'), url(r'^password-reset/done/$', 'django.contrib.auth.views.password_reset_done', name='password_reset_done'), url(r'^password-reset/confirm/(?P<uidb64>[-w]+)/(?P<token>[-w]+)/$', 'django.contrib.auth.views.password_reset_confirm', name='password_reset_confirm'), url(r'^password-reset/complete/$', 'django.contrib.auth.views.password_reset_complete', name='password_reset_complete'),
Add a new file in the templates/registration/
directory of your account
application and name it password_reset_form.html
. Add the following code to it:
{% extends "base.html" %} {% block title %}Reset your password{% endblock %} {% block content %} <h1>Forgotten your password?</h1> <p>Enter your e-mail address to obtain a new password.</p> <form action="." method="post"> {{ form.as_p }} <p><input type="submit" value="Send e-mail"></p> {% csrf_token %} </form> {% endblock %}
Now, create another file in the same directory and name it password_reset_email.html
. Add the following code to it:
Someone asked for password reset for email {{ email }}. Follow the link below: {{ protocol }}://{{ domain }}{% url "password_reset_confirm" uidb64=uid token=token %} Your username, in case you've forgotten: {{ user.get_username }}
This is the template that will be used to render the e-mail sent to the user to reset their password.
Create another file in the same directory and name it password_reset_done.html
. Add the following code to it:
{% extends "base.html" %} {% block title %}Reset your password{% endblock %} {% block content %} <h1>Reset your password</h1> <p>We've emailed you instructions for setting your password.</p> <p>If you don't receive an email, please make sure you've entered the address you registered with.</p> {% endblock %}
Create another template and name it password_reset_confirm.html
. Add the following code to it:
{% extends "base.html" %} {% block title %}Reset your password{% endblock %} {% block content %} <h1>Reset your password</h1> {% if validlink %} <p>Please enter your new password twice:</p> <form action="." method="post"> {{ form.as_p }} {% csrf_token %} <p><input type="submit" value="Change my password" /></p> </form> {% else %} <p>The password reset link was invalid, possibly because it has already been used. Please request a new password reset.</p> {% endif %} {% endblock %}
We check if the provided link is valid. Django reset password view sets this variable and puts it in the context of this template. If the link is valid, we display the user password reset form.
Create another template and name it password_reset_complete.html
. Enter the following code into it:
{% extends "base.html" %} {% block title %}Password reset{% endblock %} {% block content %} <h1>Password set</h1> <p>Your password has been set. You can <a href="{% url "login" %}">log in now</a></p> {% endblock %}
Finally, edit the registration/login.html
template of the account
application and add the following code after the <form>
element:
<p><a href="{% url "password_reset" %}">Forgotten your password?</a></p>
Now, open http://127.0.0.1:8000/account/login/
in your browser and click the Forgotten your password? link. You should see the following page:
At this point, you need to add an SMTP configuration to the settings.py
file of your project, so that Django is able to send e-mails. We have seen how to add e-mail settings to your project in Chapter 2, Enhancing Your Blog with Advanced Features. However, during development, you can configure Django to write e-mails to the standard output instead of sending them through an SMTP server. Django provides an e-mail backend to write e-mails to the console. Edit the settings.py
file of your project and add the following line:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
The EMAIL_BACKEND
setting indicates the class to use to send e-mails.
Go back to your browser, enter the e-mail address of an existing user, and click the Send e-mail button. You should see the following page:
Take a look at the console where you are running the development server. You will see the generated e-mail as follows:
IME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Subject: Password reset on 127.0.0.1:8000 From: webmaster@localhost To: [email protected] Date: Thu, 24 Sep 2015 14:35:08 -0000 Message-ID: <[email protected]> Someone asked for password reset for email [email protected]. Follow the link below: http://127.0.0.1:8000/account/password-reset/confirm/MQ/45f-9c3f30caafd523055fcc/ Your username, in case you've forgotten: zenx
The e-mail is rendered using the password_reset_email.html
template we created earlier. The URL to reset your password includes a token that was generated dynamically by Django. Open the link in your browser. You should see the following page:
The page to set a new password corresponds to the password_reset_confirm.html
template. Fill in a new password and click the Change my password button. Django creates a new encrypted password and saves it in the database. You will see a success page like this one:
Now, you can log in back to your account using your new password. Each token to set a new password can be used only once. If you open the link you received again, you will get a message telling you the token is invalid.
You have integrated the views of the Django authentication framework in your project. These views are suitable for most cases. However, you can create your own views if you need a different behavior.