User authentication and auto-management

In the first section of this chapter you learned how to create users. But recall that for this process to work, you either had to use a Django command line tool or the Django admin. These techniques while valid are not intended and won't scale for end users that visit an application.

Similarly, up to this point in the chapter, the only location for users to log in and log out has been through the Django admin authentication form. This last form is not an ideal location for end users either, not only because it lacks a layout made for an application, but also because it's not intended for users who never plan to interact with a project's database.

If you plan on end users authenticating themselves into an application, you need to provide a way for users to sign up, a way for users to log in and log out as well as a way for users to remember and change their passwords. The same django.contrib.auth package that supports the User model, also includes a series of pre-built constructs designed to create user authentication workflows.

The first pre-built mechanism available to authenticate users is a set of urls that allow users to: log in and log out of an application, allow users to change their password, as well as allow users to reset their password supported by an email notification.

Listing 10-11 illustrates how to add the full set of django.contrib.auth urls to the main urls.py file -- using an include statement -- as well as the equivalent individual url statements in case you want to selectively pick and choose which urls to use in a project.

Listing 10-11. Configure urls from django.contrib.auth package.

from django.conf.urls import url
from django.contrib.auth import views

# Option 1 to include all urls (See option 2 for included urls)
urlpatterns = [
    url(r'^accounts/', include('django.contrib.auth.urls')),
]

# Option 2) (Explicit urls, all included in django.contrib.auth)
urlpatterns = [
    url(r'^accounts/login/$', views.LoginView.as_view(), name='login'),
    url(r'^accounts/logout/$', views.LogoutView.as_view(), name='logout'),
    url(r'^accounts/password_change/$', views.PasswordChangeView.as_view(), name='password_change'),
    url(r'^accounts/password_change/done/$', views.PasswordChangeDoneView.as_view(), name='password_change_done'),
    url(r'^accounts/password_reset/$', views.PasswordResetView.as_view(), name='password_reset'),
    url(r'^accounts/password_reset/done/$', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
    url(r'^accounts/reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
        views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
    url(r'^accounts/reset/done/$', views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
]

The first option in listing 10-11 configures all the django.contrib.auth urls on the accounts url. This begets the question, why the accounts url ? Because, by default all django.contrib.auth actions use this url as their root. (e.g. in previous sections, recall that attempting to access resources that require log in, performs a redirect to /accounts/login). So by using this url include() statement with django.contrib.auth.urls, all django.contrib.auth actions get a functioning url, that are also backed by the necessary view method logic! But more on view method logic shortly.

Because the url include() statement has a 'use all urls or none' behavior, the second option in listing 10-11 represents the use of granular url statements to configure the same django.contrib.auth urls. This last option is helpful if you want to disable certain django.contrib.auth urls but still keep other django.contrib.auth urls active (e.g. disable password changing urls, but keep log in and log out urls).

As you can see, using either option in listing 10-11 provides you with a quick solution to hook-up django.contrib.auth actions to urls, the latter of which are also hooked up to default class-based views that are also part of the django.contrib.auth package.

Log in and log out workflow

The entry points into the log in and log out workflows -- as you can see in listing 10-11 -- are the /accounts/login/ and /accounts/logout/ urls, respectively. If a call is made on the /accounts/login/ url the django.contrib.auth.views.LoginView class-based view is triggered, and if a call is made on the /accounts/logout/ url the django.contrib.auth.views.LogoutView class-based view is triggered.

Like all Django built-in class-based views, both the LoginView and LogoutView class-based views require little to nothing in terms of code and configuration. The only thing they both require is a template to present a login form and a template with a logout success message, respectively.

The LoginView class-based view looks for the template registration/login.html under a directory defined as part of the TEMPLATES/DIRS variable in settings.py. And the LogoutView class-based view looks for the template registration/logout.html, also under a directory defined as part of the TEMPLATES/DIRS variable in settings.py.

Tip See the book's accompanying source code for the layout and fields required by the registration/login.html and registration/logout.html templates.

The log in workflow offers two configuration options in settings.py that allow you to modify its behavior without the need to customize the LoginView class-based view. The LOGIN_URL variable defaults to /accounts/login/ and is used as the url where users are redirected when they attempt to access resources that require authentication and aren't logged in. The LOGIC_REDIRECT variable defaults to /accounts/profile/ and is the the url where users are redirected to after a successful log in. The django.contrib.auth package provides no entry point for the /accounts/profile/ url, so you must either configure this url or simply change it to a different location (e.g. LOGIN_REDIRECT='/' to redirect users to the home page after a successful log in).

The log out workflow supports the LOGOUT_REDIRECT variable in settings.py to define where users are taken when they log out. The LOGOUT_REDIRECT variable defaults to /accounts/logout/, but can be updated to redirect users to a different location (e.g. LOGOUT_REDIRECT='/' to redirect users to the home page after they log out).

Other than creating the templates, setting up the urls -- described in listing 10-11 -- and optionally changing the log in url location and log in/out redirect behaviors, there is nothing else you need to do to enable the log in and log out workflows provided by the django.contrib.auth package. If you want a user to log in, simply point them to the /accounts/login/ url and if you want them to log out point them to the /accounts/logout/ url. All the other workflow details (e.g. authentication, password verification, error form handling, session expiration) are taken care of by the LoginView and LogoutView class-based views.

Password change workflow

The password change workflow requires two url entry points. The /accounts/password_change/ url which triggers the PasswordChangeView class-based view and the /accounts/password_change/done/ url which triggers the PasswordChangeDoneView class-based view.

The PasswordChangeView class-based view looks for a template containing a form to change passwords in registration/password_change_form.html under a directory defined as part of the TEMPLATES/DIRS variable in settings.py. And the PasswordChangeDoneView class-based view looks for a template containing a success message in registration/password_change_done.html, also under a directory defined as part of the TEMPLATES/DIRS variable in settings.py.

Tip See the book's accompanying source code for the layout and fields required by the registration/password_change_form.html and registration/password_change_done .html templates.

Other than creating the templates and setting up the urls -- described in listing 10-11 -- there is nothing else you need to do to enable the password change workflow provided by the django.contrib.auth package. If you want a user to change his password, simply point them to the /accounts/password_change/ url. All the other workflow details (e.g. password verification, database update, form error handling) are taken care of by the PasswordChangeView and PasswordChangeDoneView class-based views.

Password reset workflow

The password reset workflow consists of two sub-workflows. The first sub-workflow captures a user's email and sends him an email with a link to reset his password. The second sub-workflow processes the reset link and validates a new password for the user.

The first sub-workflow uses the /accounts/password_reset/ url which triggers the PasswordResetView class-based view and the /accounts/password_reset/done/ url which triggers the PasswordResetDoneView class-based view. The second sub-workflow uses the /accounts/reset/ url which triggers the PasswordResetConfirmView class-based view and the /accounts/reset/done/ url which triggers the PasswordResetCompleteView class-based view.

The PasswordResetView class-based view looks for a template containing a form to reset a user's password in registration/password_reset_form.html and the PasswordResetDoneView class-based view looks for a success message template in registration/password_reset_done.html, both under a directory defined as part of the TEMPLATES/DIRS variable in settings.py. For the second sub-workflow, the PasswordResetConfirmView class-based view looks for a template with a form to introduce a new user password in registration/password_reset_confirm.html and the PasswordResetCompleteView class-based view looks for a success message template in registration/password_reset_complete.html, both under a directory defined as part of the TEMPLATES/DIRS variable in settings.py.

By default, the password reset workflow generates an email with instructions to take a user to the second sub-workflow to reset his password. Also by default, the reset email contains a link to the domain localhost:8000, which can be customized by installing the Django site django.contrib.sites app (e.g. add django.contrib.sites to INSTALLED_APPS and SITE_ID=1 in settings.py, and update the site with id 1 in the Django admin to reflect a new domain). Additionally, you can define a custom email layout in the registration/password_reset_email.html template and place it under a directory defined as part of the TEMPLATES variable in settings.py.

Tip See the book's accompanying source code for the layout and fields required by the registration/password_reset_form.html, registration/password_reset_done.html, registration/password_reset_confirm.html, registration/password_reset_complete.html, and registration/password_reset_email.html templates.

Other than creating the templates and setting up the urls -- described in listing 10-11 -- and optionally changing the default email layout, there is nothing else you need to do to enable to allow users to remember their passwords using workflow provided by the django.contrib.auth package. If you want a user to remember his password, simply point them to the /accounts/password_reset/ url. All the other workflow details (e.g. email token validation, password verification, database update, form error handling) are taken care of by the PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView and PasswordResetCompleteView class-based views.

User sign up workflow

Users can sign up automatically to a Django application with the help a few constructs from the django.contrib.auth package. Although the user sign up workflow is not as baked-in to the django.contrib.auth package as the previous user related workflows, it's still easy to create.

The first step to create a user sign up workflow is to configure a url entry point to allow users to create an account, as illustrated in listing 10-12.

Listing 10-12. Configure url for user sing up workflow.

# urls.py main
from django.conf.urls import url
from django.contrib.auth import views

from coffeehouse.registration import views as registration_views
urlpatterns = [
    url(r'^accounts/', include('django.contrib.auth.urls')),
    url(r'^accounts/signup/$',registration_views.UserSignUp.as_view(),name="signup"),    
]

Listing 10-12 illustrates a url accessible at /accounts/signup/, in addition to all the urls provided by the django.contrib.auth package that include the previous user related workflows. Notice the /accounts/signup/ url is set to be processed by the UserSignUp class-based view from the coffeehouse.registration app, which means it's a class-based view you need to create for the project.

Listing 10-13 illustrates the UserSignUp class-based view tasked with the user sign up workflow, which automates the creation of User model records, that was previously done using the Django command line tool or the Django admin.

Listing 10-13. Sign-up workflow fulfilled by custom CreateView class-based view.

from django.core.urlresolvers import reverse_lazy
from django.http import HttpResponseRedirect
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login

class UserSignupForm(UserCreationForm):
    email = forms.EmailField(required=True)
    class Meta:
        model = User
        fields = ("username", "email", "password1", "password2")
        
class UserSignUp(SuccessMessageMixin,CreateView):
    model = User
    form_class = UserSignupForm
    success_url = reverse_lazy('items:index')
    success_message = "User created successfully"
    template_name = "registration/signup.html"
    def form_valid(self, form):
        super(UserSignUp,self).form_valid(form)        
        # The form is valid, automatically sign-in the user
        user = authenticate(self.request, username=form.cleaned_data['username'], 
                                                 password=form.cleaned_data['password1'])
        if user == None:
            # User not validated for some reason, return standard form_valid() response
            return self.render_to_response(self.get_context_data(form=form))            
        else:
            # Log the user in
            login(self.request, user)
            # Redirect to success url
            return HttpResponseRedirect(self.get_success_url())

The UserSignUp class-based view in listing 10-13 is a standard CreateView class-based view that uses the mixin SuccessMessageMixin -- if you're unfamiliar with this type of class-based view to create model records or the concept of mixins, see the previous chapter which explains both these topics.

The UserSignUp class-based view sets the model option to User to tell Django to create django.contrib.auth.models.User records. Next, the form_class option is set to UserSignupForm which is also defined in listing 10-13. Followed are the success_url and success_message options to indicate a url and success message when a User record record is created, as well as the template_name to specify a template with the form presented to capture the User fields.

The custom UserSignupForm model form in listing 10-13 is based on the UserCreationForm from the django.contrib.auth package. From a practical standpoint, the UserSignUp class-based view could have used UserCreationForm as its form_class, however, this last built-in form only contains username and password fields. In this case, to solicit an email as part of the user sign up process, the custom UserSignupForm model form adds the email field to the base UserCreationForm form class. Note that because the backing User model already includes an email field, no modification is required to the model.

In order to automatically sign in users once a User model record is created, the UserSignUp class-based view includes custom logic in its form_valid() method. In the case of listing 10-13, the authenticate method from the django.contrib.auth package is invoked with the form values, verifying the credentials are valid. If the credentials are valid -- which given the workflow should be 100% of the time, unless an unforeseen hacking event takes place -- the authenticate method returns a User instance and immediately executes the login method -- also from the django.contrib.auth package -- which creates a session (i.e. signs in) the user, after which control is redirect to the success page of the class-based view. In the unforeseen event the authenticate method doesn't return a User instance, the form_valid() method returns its standard payload which is the validated form.

As you can see from listing 10-12 and listing 10-13, you can create a user sign up workflow to let users create their own accounts and lift the administrative burden of creating user accounts from the Django admin or Django command line tool.

Tip The sign up workflow is listing 10-13 is permissive in the sense it doesn't verify emails and automatically signs users in. This was done for simplicity and may work for most projects as is. But you can add extra safeguards to the sign up workflow (e.g. send a verification link, set users to inactive until verification link is clicked) by adding this logic to the different class-based view methods, before or after the User record is created.
Tip The Django allauth package, described later in this chapter, has built-in support for email verification.