Basic Django Authentication

Django comes with a built-in authentication system, with which you can let users register and login to your website. The Django authentication consists of many features, including Permissions, Groups and the ability to extend and customize it to suit your needs. In this post; however, I will only discuss it's default implementation to the extend that users will be able to register, login, logout and reset or change their passwords.

Warming Up

Django provides everything that we need to use in the django.contrib.auth module. Now this isn't necessary, but if you can find where Django is installed and see where the auth module is located, it can help you better understand what I'll be showing you today. In case you are using a virtual environment you can go to Lib\site-packages to find all the installed packages in your virtual environment. Then inside that go to django\contrib\auth directory and you can see all the models, views, urls and everything else associated with it.

Now let's get started.

Getting Started

In the projects urls.py, add the following in the urlpatterns, like so:

urlpatterns = [
    # other patterns
    url(r'^accounts/', include('django.contrib.auth.urls')),
    # other patterns
]

With this we will be telling Django to use the urlpatterns defined in urls.py file in django.contrib.auth. Following are the urlpatterns defined there:

^login/$ [name='login']
^logout/$ [name='logout']
^password_change/$ [name='password_change']
^password_change/done/$ [name='password_change_done']
^password_reset/$ [name='password_reset']
^password_reset/done/$ [name='password_reset_done']
^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$ [name='password_reset_confirm']
^reset/done/$ [name='password_reset_complete']

Each of these urls call a View when accessed, and those views handle all the processing that we need. Except if you noticed, these do not include a url for a Sign-up view. We will create that later.

If you were to access any of the urls in your browser, such as '/login/', you will either get a TemplateDoesNotExist error, or you will be shown a page with admin styles. None of which is what we want. This is because Django does provide us with the urls and views for most of the authentication process but lets us create the templates for them, so that we can match our style.

Lets create the templates for all the above urls.

Creating Templates

Create a folder named 'registration' inside the templates folder of your project. We'll need to create a template for each of the urls/views inside this folder.

Login View

Create a file called login.html in the registration folder:

{% extends "base.html" %}
{% block title %}Login{% endblock %}

{% block content %}

    <h1>Login</h1>
    <form method="POST">{% csrf_token %}
        {{ form.as_p }}
        <input type="submit">
    </form>

{% endblock %}

The block names could be different for you, but the important part is the form. This will display the form that the LoginView sends to the template.

Now you can go to '/login/' and enter in a username and password of an existing (could be your superuser) and hit submit. You will be logged in but will probably see a Page not found error. This just means that Django tried to redirect you to '/accounts/profile/', as this is the default value of LOGIN_REDIRECT_URL. You can change this by defining your own redirect url, in settings.py file:

# Specify a url (homepage)
LOGIN_REDIRECT_URL = "/"
# Or
# Specify a named URL pattern
LOGIN_REDIRECT_URL = "home"

You can set whatever url or named-pattern you want.

And now if you try to login again at '/login/', you should be redirected to whatever url you set in the LOGIN_REDIRECT_URL.

Note: If you don't have a user to test this with, you can create a user from your console with the following command:

$ python manage.py createsuperuser

Logout View

Once you are logged in, you can log out by going to 'accounts/logout' and you will see an admin styled page informing you that you have logged out. There are two basic ways to change this. You can either create a logged_out.html template, or you can set a value to LOGOUT_REDIRECT_URL in the settings.py file.

Option 1: Create logged_out.html template in the 'registration' folder:

{% extends "base.html" %}
{% block title %}Logged Out{% endblock %}

{% block content %}
    <p>You have been logged out.</p>
{% endblock %}

Option 2: Set a value to LOGOUT_REDIRECT_URL in settings.py file:

LOGOUT_REDIRECT_URL = "home"

Again, this can be whatever url or named-pattern you want.

Password Change Views

These set of views let users change their passwords, by providing their old password and setting a new one.

Password Change

This view at 'accounts/password_change' shows a form to enter in the passwords.

Create password_change_form.html in the same 'registration' folder:

{% extends "base.html" %}
{% block title %}Password change{% endblock %}

{% block content %}

    <h1>Change your password</h1>
    <form method="POST">{% csrf_token %}
        {{ form.as_p }}
        <input type="submit" value="Change password">
    </form>

{% endblock %}

Password Change Done

Once the password has been changed, this view will let the user know about it.

Create password_change_done.html in the registration folder:

{% extends "base.html" %}
{% block title %}Password change successful{% endblock %}

{% block content %}
    <p>Your password has been changed successfully</p>
{% endblock %}

Password Reset Views

These views let users request a password reset by entering the email address associated with their account. The user then receives an email with link to confirm the password reset. On that linked page they can enter in a new password, and their password gets changed.

Password Reset

This view shows the user a form to enter the email address of their account.

Create password_reset_form.html in the 'registration' folder:

{% extends "base.html" %}
{% block title %}Password change{% endblock %}

{% block content %}

    <h1>Forgotten your password?</h1>
    <p>Enter the email address of your account and we'll send you instructions.</p>
    <form method="POST">{% csrf_token %}
        {{ form.as_p }}
        <input type="submit" value="Reset Password">
    </form>

{% endblock %}

If you do not have an Email-backend set up, you can easily create a File based backend and test this process. In your settings.py, add:

EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = os.path.join(BASE_DIR, "sent_emails")

This will create a "sent_emails" directory in your project's root, and you will be able to see all the emails sent from this process.

Password Reset Done

This view just asks the user to check his/her inbox for the instructions.

Create password_reset_done.html in the registration folder:

{% extends "base.html" %}
{% block title %}Email sent{% endblock %}

{% block content %}
    <p>Please check your inbox for the instructions to reset your password.</p>
{% endblock %}

Password Reset Confirm

This view lets the user enter in a new password

Create password_reset_confirm.html in the 'registration' folder:

{% extends "base.html" %}
{% block title %}Change your Password{% endblock %}

{% block content %}

    <h1>Set a new password</h1>
    <form method="POST">{% csrf_token %}
        {{ form.as_p }}
        <input type="submit" value="Set Password">
    </form>

{% endblock %}

Password Reset Complete

This view informs the user that their new password has been set.

Create password_reset_complete.html in the registration folder:

{% extends "base.html" %}
{% block title %}Password Change Complete{% endblock %}

{% block content %}
    <h1>All done</h1>
    <p>Your password has been changed.</p>
{% endblock %}

With this we are done using the built-in urls that Django provides. Next we will see how to create a Sign-Up view.

Creating Custom Views

If we want to create custom views, or even override some of the default views, we will have to create an app.

Create an app

  • In your console, create an app called accounts:
python manage.py startapp accounts
  • In settings.py add 'accounts' to INSTALLED_APPS:
INSTALLED_APPS = [
    # Default apps above
    'accounts',
    # Your custom apps below
]
  • In your project's urls.py, add a pattern to account's urls above django.contrib.auth.urls, like so:
url(r'^accounts/', include("accounts.urls", namespace="accounts")), # Line to add
url(r'^accounts/', include('django.contrib.auth.urls')), # You already have this line
  • Create a urls.py file in the 'accounts' stub, with empty urlpatterns, for now:
from django.conf.urls import url
from .import views

urlpatterns = [
]
  • In the accounts stub, create a 'templates' folder and in that create an 'accounts' folder.
  • In the accounts stub, create an empty forms.py file.

Creating the Sign-Up View

The first thing that we need to do is create a url to point to a custom view.

Creating the url

In account's urls.py file, add the url pattern, like so:

urlpatterns = [
    url(r'^signup/$', views.SignUpView.as_view(), name="signup"),
]

So now if a user types 'accounts/signup/', Django will look for a view named 'SignUpView' in the app's views.py file. So lets create that.

Creating the View

In account's views.py:

from django.core.urlresolvers import reverse_lazy
from django.views import generic

from . import froms


class SignUpView(generic.CreateView):
    form_class = forms.UserCreateForm
    success_url = reverse_lazy("login")
    template_name = "accounts/signup.html"

This is a class-based CreateView, that we basically use to create a user.

  • With form_class, we specified a custom form name UserCreateForm, which we will create next.
  • success_url tells Django where to redirect the user once the form is successfully submitted without errors, in this case we redirect the user to the Login page.
  • template_name is the template we will create to output the form.

Creating the Form

In the accounts' forms.py file:

from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User


class UserCreateForm(UserCreationForm):
    class Meta:
        fields = ("username", "email", "password1", "password2")
        model = User

The UserCreationForm that our form inherits handles things like validation and saving of the form. And we just have to define what fields we want to display in the form and the model that we want to use. password1 and password2 are the field names of the two password fields that the user needs to match.

Creating the Template

In 'templates/accounts', that you had created earlier, create signup.html:

{% extends "base.html" %}
{% block title %}Sign Up{% endblock %}

{% block content %}

    <h1>Sign Up</h1>
    <form method="POST">{% csrf_token %}
        {{ form.as_p }}
        <input type="submit" value="Sign Up">
    </form>

{% endblock %}

As always it's super simple to just display a form in a template.

The Sign Up view is now complete. Going to '/accounts/signup/', it will show a form with 'Username', 'Email', 'Password' and 'Password Confirmation' input fields. Once a user fills and submits the form, it will either display errors, if any; or create an instance in the User model and redirect the user to the login page.

Overriding Views

As a last bit of exercise and since we have already created an app to create our SignUpView, let me quickly show you how you might override a default view that we have been using from django.contrib.auth.urls.

Overriding LogoutView

If you look at the url patterns from django.contrib.auth.urls posted at the top, you can see that the pattern r'^logout/$' with name=logout calls LogoutView. Lets take over this url pattern and create our own LogoutView in which we add a message to let the user know that they've been logged out.

In the account's urls.py, add:

url(r'^logout/$', views.LogoutView.as_view(), name="logout"),

This will call LogoutView from account's views.py, that we will create now. But understand that the reason why this pattern will overide the default one is because in the project's urls.py file, we have put accounts.urls above django.contrib.auth.urls for the patter '^accounts/'.

In the account's views.py, we will make some imports and create our LogoutView. Making the views.py look like:

from django.contrib.auth import logout # Added now
from django.contrib import messages # Added now
from django.core.urlresolvers import reverse_lazy
from django.views import generic

from . import forms


# Added now
class LogoutView(generic.RedirectView):
    url = reverse_lazy('home')
    def get(self, request, *args, **kwargs):
        logout(request)
        messages.add_message(self.request, messages.SUCCESS, 'You have been logged out.')
        return super().get(request, *args, **kwargs)

class SignUpView(generic.CreateView):
    form_class = forms.UserCreateForm
    success_url = reverse_lazy("login")
    template_name = "accounts/signup.html"
  • At the top of the file we import the logout function and the messages function.
  • The LogoutView uses the Redirect class-based view.
  • The url to redirect to is set to "home", which presumably is the name-pattern of the homepage.
  • The get() method runs, on a GET request, ie. when the page loads.
  • In the get() method we destroy the user's session using logout() and add the message that we want to display.

Now to actually display the message, we need to add the following in a template that the home page might be using:

{% if messages %}
<div class="messages">
    {% for message in messages %}
        <span{% if message.tags %} class="gia-alert {{ message.tags }}"{% endif %}>{{ message }}</span>
    {% endfor %}
</div>
{% endif %}

Now your users will see a message whenever they log out.

With this we complete our very basic example of how you can have a custom view for an authentication process, and with it we complete this tutorial. There are many things you can change in Django's authentication system, and there are many ways to do that, including completely overriding it. In this tutorial I've shown just a few ways of using the system and there are many things you can learn to do with it.

Final thoughts

The authentication system that we have created now is good, but it's still quite limited. For example, we do not have an email verification process for only letting users sign up once they have verified their provided email. There are many third party libraries that you can use for this. The very famous library django-allauth not only provides extra options like email verification on signup, but also provides Social Authentication that allows users to login with their social accounts, and integrates them really well.

You can read more about Django Authentication from their docs.

Hope this was useful to you and thank you for reading!

blog comments powered by Disqus