PROJECTS NOTES HOME

Getting to know to built in django authentication system

This is basically a copy of https://learndjango.com/tutorials/django-login-and-logout-tutorial tutorial. Wanted to keep a copy of it for myself, since this information is useful.

This was the first version of how I configured authentication in my Django_starter app, but then I discovered allauth and everything mentioned in this post was replaced by it. The concepts below are still good to know and are very relevant.

1 Get to know to Django's built-in in authentication system

In this section, we'll configure a complete user authentication system in Django consisting of login, logout, signup, password change, and password reset.

The Django contrib q module provides built-in apps to help with development. In the project/settings.py file under INSTALLED_APPS, you can see that auth is listed and available to us.

# project/settings.py
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",  # THIS!!!!
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

To use the auth app, we need to add it to our project-level project/urls.py file. At the top, import include and create a new URL path at accounts/. You can choose a different URL path, but using accounts/ is a standard practice and requires less customization later.

# project/urls.py
from django.contrib import admin
from django.urls import path, include  # new

urlpatterns = [
    path("admin/", admin.site.urls),
    path("accounts/", include("django.contrib.auth.urls")),  # new
]

The auth app we've now included provides us with multiple authentication views and URLs for handling login, logout, password change, password reset, etc. It notably does not include a view and URL for signup, so we have to configure that ourselves.

accounts/login/ [name='login']
accounts/logout/ [name='logout']
accounts/password_change/ [name='password_change']
accounts/password_change/done/ [name='password_change_done']
accounts/password_reset/ [name='password_reset']
accounts/password_reset/done/ [name='password_reset_done']
accounts/reset/<uidb64>/<token>/ [name='password_reset_confirm']
accounts/reset/done/ [name='password_reset_complete']

2 Log In functionality

Let's make our login page! By default, Django will look within a templates folder called registration for auth templates. The login template is called login.html.

Inside of the templates directory create a folder called registration:

Then create a login.html file and include the following code:

<!-- templates/registration/login.html -->
<h2>Log In</h2>
<form method="post">
  {% csrf_token %}
  {{ form }}
  <button type="submit">Log In</button>
</form>

This code is a standard Django form using POST to send data and {% csrf_token %} tags for security concerns, namely to prevent a CSRF Attack. The form's contents are displayed with {{ form }}, and then we add a "submit" button.

Our login functionality now works, but we should specify where to redirect the user upon a successful login using the LOGIN_REDIRECT_URL setting. At the bottom of the settings.py file, add the following to redirect the user to the homepage.

# project/settings.py
LOGIN_REDIRECT_URL = "index"  # new

If you start the Django server again with python manage.py runserver and navigate to our login page at http://127.0.0.1:8000/accounts/login/, you'll see the login page.

Let's add a navigation link to the login page. In navbar.html add this line:

<li><a href="{% url 'login' %}">login</a></li>

We can only log in if we have a user account. And since adding a signup form is yet to come, the most straightforward approach is to make a superuser account from the command line. Quit the server with Control+c and then run the command python manage.py createsuperuser. Answer the prompts and note that your password will not appear on the screen when typing for security reasons.

# let's make migration first, to create tables in the database
python manage.py makemigrations
python manage.py migrate

python manage.py createsuperuser

# Username (leave blank to use 'root'):
# Email address:
# Password:
# Password (again):
# Superuser created successfully.

Now start the server again with python manage.py runserver and refresh the page at http://127.0.0.1:8000/accounts/login/. Enter the login info for your just-created superuser.

Our login worked because it redirected us to the homepage which we have created earlier.

3 Log Out functionality

But how do we log out? The only option currently is to go into the admin panel at http://127.0.0.1:8000/admin/ and click the "Log Out" link in the upper right corner. The "Logout" link will log us out.

NOTE: One of the changes to Django 5.0, as noted in the release notes, is the removal support for logging out via GET requests. In previous versions of Django, you could add a logout link like <a href" {% url 'logout' %}">Log Out</a>= to a template file. But now a POST request via a form is required.

NOTE NOTE: but if using "allauth" package, you can simply use the 'account_logou' url.

We already have this in our base.html:

<form action="{% url 'logout' %}" method="post">
  {% csrf_token %}
  <button type="submit">Log Out</button>
</form>

Then we need to update settings.py with our redirect link, LOGOUT_REDIRECT_URL. Add it right next to our login redirect so the bottom of the settings.py file should look as follows:

# project/settings.py
LOGIN_REDIRECT_URL = "index"
LOGOUT_REDIRECT_URL = "index"  # new

4 Sign Up functionality

Now that we have sorted out logging in and logging out, it is time to add a signup page to our basic Django site. If you recall, Django does not provide a built-in view or URL for this, so we must code up the form and the page ourselves.

To begin, stop the local webserver with Control+c and create a dedicated app called accounts, which we'll use for our custom account logic.

python manage.py startapp accounts

We then move the newly created accounts app into apps folder for better structure in the future. All the apps will be in one folder.

Go to apps.py and fix the name variable to be name = "apps.accounts". From now on if we want to refernece urls of this app, we will do so by writing apps.accounts.urls.

Make sure to add the new app to the INSTALLED_APPS setting in the project/settings.py file:

# project/settings.py
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "apps.accounts",  # new
]

Then add a URL path in project/urls.py that is above our included Django auth app. The order is important here because Django looks for URL patterns from top-to-bottom. We want to maintain the pattern of having our user authentication logic at accounts/ but ensure that the signup page loads first.

# django_project/urls.py
from django.contrib import admin
from django.urls import path, include

from project.views import Index

urlpatterns = [
    path("admin/", admin.site.urls),
    path("accounts/", include("apps.accounts.urls")),  # new
    path("accounts/", include("django.contrib.auth.urls")),
    path("", Index.as_view(), name="index"),
]

Next, create a new file called accounts/urls.py with your text editor and add the following code.

# accounts/urls.py
from django.urls import path

from .views import SignUpView


urlpatterns = [
    path("signup/", SignUpView.as_view(), name="signup"),
]

Now for the accounts/views.py file:

# accounts/views.py
from django.contrib.auth.forms import UserCreationForm
from django.urls import reverse_lazy
from django.views.generic import CreateView


class SignUpView(CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy("login")
    template_name = "registration/signup.html"

At the top we import UserCreationForm, reverse_lazy, and the generic class-based view CreateView.

We are creating a new class called SignUpView that extends CreateView, sets the form as UserCreationForm, and uses the not-yet-created template signup.html. Note that we use reverse_lazy to redirect users to the login page upon successful registration rather than reverse, because for all generic class-based views, the URLs are not loaded when the file is imported, so we have to use the lazy form of reverse to load them later when we are sure they're available.

Ok, now for the final step. Create a new template, templates/registration/signup.html, and populate it with this code that looks almost exactly like what we used for login.html.

<!-- templates/registration/signup.html -->
{% extends "base.html" %}

{% block title %}Sign Up{% endblock %}

{% block content %}
<h2>Sign up</h2>
<form method="post">
  {% csrf_token %}
  {{ form }}
  <button type="submit">Sign Up</button>
</form>
{% endblock %}

We're done! To confirm it all works, spin up our local server with python manage.py runserver and navigate to http://127.0.0.1:8000/accounts/signup/.

Sign up for a new account and hit the "Sign up" button. You will be redirected to the login page, http://127.0.0.1:8000/accounts/login/, where you can log in with your new account.

And then, after a successful login, you'll be redirected to the homepage.

5 Password Change

Django provides a default implementation of password change functionality. To try it out, log out of your superuser account and log in with your regular user.

The default "Password change" page is located at http://127.0.0.1:8000/accounts/password_change/.

Enter your old password and then a new one twice. Click the "Change My Password" button, and you will be redirected to the "Password change successful" page.

If you want to customize these two password change pages to match the look and feel of your website, it is only necessary to override the existing templates. Django already provides us with the views and URLs. To do this, create two new template files in the registration directory:

  • templates/registration/password_change_form.html
  • templates/registration/password_change_done.html

We can add a password change link to the base.html.

6 Password Reset

A password reset page is useful when a user forgets their log in information: a user can enter in their email address and receive a cryptographically secure email with a one-time link to a password reset page. This is typically available to logged-out users. Django has built-in functionality for this that only requires a small amount of configuration.

Let's add a link to the default password reset page that will be available to logged-out users.

We can add a password reset link to the base.html.

Click on the link for "Password Reset."

The default template is ugly and styled to match the admin but is functional. We want to try it out, but there's one problem: our regular user account does not have an email address associated with it. The default Django UserCreationForm we extended for our signup form does not have email included!

Nonetheless, there is an easy fix. Log in to the admin, click on Users, and select the username for your regular user account to bring up the change user page where you can add an email.

Make sure to click the "Save" button at the bottom of the page. Then click the "Log Out" button in the upper right-hand corner of the admin or back on the homepage.

Django defaults to an SMTP email backend that requires some configuration. To test the password reset flow locally, we can update the django_project/settings.py file to output emails to the console instead. Add this one line to the bottom of the file.

# django_project/settings.py
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" # new

Finally, we can try the Password Reset page again at http://127.0.0.1:8000/accounts/password_reset/. Enter the email address for your regular user account and click the "Change My Password" button. It will redirect you to the password reset sent page.

For security reasons, Django will not provide any notification whether you entered an email that exists in the database or not. But if you look in your terminal/console now, you can see the contents of the email outputted there.

Copy the unique URL from your console into your web browser. It will cryptographically confirm your identity and take you to the Password Reset Confirmation page at http://127.0.0.1:8000/accounts/reset/Mg/set-password/.

Enter in a new password and click the "Change my password" button. It will redirect you to the Password reset complete page.

To confirm everything worked correctly, navigate to the homepage and log in to your account with the new password.

If you want to customize the templates involved with password reset, they are located at the following locations; you need to create new template files to override them.

  • templates/registration/password_reset_confirm.html
  • templates/registration/password_reset_form.html
  • templates/registration/password_reset_done.html