4

Building a Social Website

In the preceding chapter, you learned how to create sitemaps and feeds, and you built a search engine for your blog application. In this chapter, you will discover how to develop a social application, which means that users are able to join an online platform and interact with each other by sharing content. Over the next few chapters, we will focus on building an image sharing platform. Users will be able to bookmark any image on the Internet and share it with others. They will also be able to see activity on the platform from the users they follow and like/unlike the images shared by them.

In this chapter, we will start by creating a functionality for users to log in, log out, edit their password, and reset their password. You will learn how to create a custom profile for your users and you will add social authentication to your site.

This chapter will cover the following topics:

  • Using the Django authentication framework
  • Creating user registration views
  • Extending the user model with a custom profile model
  • Adding social authentication with Python Social Auth

Let's start by creating your new project.

Creating a social website project

You are going to create a social application that will allow users to share images that they find on the Internet. You will need to build the following elements for this project:

  • An authentication system for users to register, log in, edit their profile, and change or reset their password
  • A follow system to allow users to follow each other on the website
  • A functionality to display shared images and implement a bookmarklet for users to share images from any website
  • An activity stream that allows users to see the content uploaded by the people that they follow

This chapter will address the first point on the list.

Starting your social website project

Open the terminal and use the following commands to create a virtual environment for your project and activate it:

mkdir env
python3 -m venv env/bookmarks
source env/bookmarks/bin/activate

The shell prompt will display your active virtual environment, as follows:

(bookmarks)laptop:~ zenx$

Install Django in your virtual environment with the following command:

pip install "Django==3.0.*"

Run the following command to create a new project:

django-admin startproject bookmarks

The initial project structure has been created. Use the following commands to get into your project directory and create a new application named account:

cd bookmarks/
django-admin startapp account

Remember that you should add the new application to your project by adding the application's name to the INSTALLED_APPS setting in the settings.py file. Place it in the INSTALLED_APPS list before any of the other installed apps:

INSTALLED_APPS = [
    'account.apps.AccountConfig',
    # ...
]

You will define Django authentication templates later on. By placing your application first in the INSTALLED_APPS setting, you ensure that your authentication templates will be used by default instead of any other authentication templates contained in other applications. Django looks for templates by order of application appearance in the INSTALLED_APPS setting.

Run the next command to sync the database with the models of the default applications included in the INSTALLED_APPS setting:

python manage.py migrate

You will see that all initial Django database migrations get applied. Next, you will build an authentication system into your project using the Django authentication framework.

Using the Django authentication framework

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 log in, log out, password change, and password reset.

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 setting of your project:

  • AuthenticationMiddleware: Associates users with requests using sessions
  • SessionMiddleware: Handles the current session across requests

Middleware are classes with methods that are globally executed during the request or response phase. You will use middleware classes on several occasions throughout this book, and you will learn how to create custom middleware in Chapter 14, Going Live.

The authentication framework also includes the following models:

  • User: A user model with basic fields; the main fields of this model are username, password, email, first_name, last_name, and is_active
  • Group: A group model to categorize users
  • Permission: Flags for users or groups to perform certain actions

The framework also includes default authentication views and forms, which you will use later.

Creating a login view

We will start this section by using the Django authentication framework to allow users to log in to your website. Your view should perform the following actions to log in a user:

  • Get the username and password that is posted by the user using a login form
  • Authenticate the user against the data stored in the database
  • Check whether the user is active
  • Log the user into the website and start an authenticated session

First, you will create a login form. Create a new forms.py file in 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 you use the PasswordInput widget to render the password HTML element. This will include type="password" in the HTML so that the browser treats it as a password input.

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(request,
                                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 the basic login view does: when the user_login view is called with a GET request, you instantiate a new login form with form = LoginForm() to display it in the template. When the user submits the form via POST, you perform the following actions:

  • Instantiate the form with the submitted data with form = LoginForm(request.POST).
  • Check whether the form is valid with form.is_valid(). If it is not valid, you display the form errors in your template (for example, if the user didn't fill in one of the fields).
  • If the submitted data is valid, you authenticate the user against the database using the authenticate() method. This method takes the request object, the username, and the password parameters and returns the User object if the user has been successfully authenticated, or None otherwise. If the user has not been authenticated, you return a raw HttpResponse, displaying the Invalid login message.
  • If the user was successfully authenticated, you check whether the user is active by accessing the is_active attribute. This is an attribute of Django's user model. If the user is not active, you return an HttpResponse that displays the Disabled account message.
  • If the user is active, you log the user into the website. You set the user in the session by calling the login() method and return the Authenticated successfully message.

Note the difference between authenticate and login: authenticate() checks user credentials and returns a User object if they are correct; login() sets the user in the current session.

Now you will need to create a URL pattern for this view. Create a new urls.py file in your account application directory and add the following code to it:

from django.urls import path
from . import views
urlpatterns = [
    # post views
    path('login/', views.user_login, name='login'),
]

Edit the main urls.py file located in your bookmarks project directory, import include, and add the URL patterns of the account application, as follows:

from django.urls import path, include
from django.contrib import admin
urlpatterns = [
    path('admin/', admin.site.urls),
    path('account/', include('account.urls')),
]

The login 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 login template. Create the following files and directories inside the account application directory:

templates/
    account/
        login.html
    base.html

Edit the base.html template and add the following code to it:

{% load static %}
<!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 you did in your previous project, 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 from the chapter's source code to the same location in your project so that you can use the static files. You can find the directory's contents at https://github.com/PacktPublishing/Django-3-by-Example/tree/master/Chapter04/bookmarks/account/static.

The base template defines a title block and a content block that can be filled with content by the templates that extend from it.

Let's fill in the template for your login 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 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 your form will be submitted via POST, you will include the {% csrf_token %} template tag for cross-site request forgery (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, email, and password. Then, run the development server using the python manage.py runserver command and open http://127.0.0.1:8000/admin/ in your browser. Access the administration site using the credentials 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:

Figure 4.1: The Django administration site index page including Users and Groups

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 login form:

Figure 4.2: The user login page

Now, submit the form, leaving one of the fields empty. In this case, you will see that the form is not valid and displays errors, as follows:

Figure 4.3: The login form with field errors

Note that some modern browsers will prevent you from submitting the form with empty or erroneous fields. This is because of form validation done by the browser based on field types and restrictions per field. In this case, the form won't be submitted and the browser will display an error message for the fields that are wrong.

If you enter a non-existent user or a wrong password, you will get an Invalid login message.

If you enter valid credentials, you will get an Authenticated successfully message, like this:

Figure 4.4: The successful authentication plain text response

Using Django authentication views

Django includes several forms and views in the authentication framework that you can use right away. 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 class-based views to deal with authentication. All of them are located in django.contrib.auth.views:

  • LoginView: Handles a login form and logs in a user
  • LogoutView: Logs out a user

Django provides the following views to handle password changes:

  • PasswordChangeView: Handles a form to change the user's password
  • PasswordChangeDoneView: The success view that the user is redirected to after a successful password change

Django also includes the following views to enable users to reset their password:

  • PasswordResetView: Allows users to reset their password. It generates a one-time-use link with a token and sends it to a user's email account.
  • PasswordResetDoneView: Tells users that an email—including a link to reset their password—has been sent to them.
  • PasswordResetConfirmView: Allows users to set a new password.
  • PasswordResetCompleteView: The success view that the user is redirected to after successfully resetting their password.

The views listed in the preceding lists 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 the built-in authentication views at https://docs.djangoproject.com/en/3.0/topics/auth/default/#all-authentication-views.

Login and logout views

Edit the urls.py file of your account application, like this:

from django.urls import path
from django.contrib.auth import views as auth_views
from . import views
urlpatterns = [
    # previous login view
    # path('login/', views.user_login, name='login'),
    path('login/', auth_views.LoginView.as_view(), name='login'),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
]

In the preceding code, you comment out the URL pattern for the user_login view that you created previously to use the LoginView view of Django's authentication framework. You also add a URL pattern for the LogoutView view.

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.

The django.contrib.admin module includes some of the authentication templates that are used for the administration site. You have placed the account application at the top of the INSTALLED_APPS setting so that Django uses your templates by default instead of any authentication templates defined in other applications.

Create a new file inside the templates/registration 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 you created before. Django uses the AuthenticationForm form located at django.contrib.auth.forms by default. This form tries to authenticate the user and raises a validation error if the login was unsuccessful. In this case, you can look for errors using {% if form.errors %} in the template to check whether the credentials provided are wrong.

Note that you have added a hidden HTML <input> element to submit the value of a variable called next. This variable is first set by the login 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 the user to the given URL after a successful login.

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 login and logout views, your website is now ready for users to log in using Django authentication views.

Now, you will create a new view to display a dashboard when users log in to 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'})

You decorate your view with the login_required decorator of the authentication framework. The login_required decorator checks whether the current user is authenticated. If the user is authenticated, it executes the decorated view; if the user is not authenticated, it redirects the user to the login URL with the originally requested URL as a GET parameter named next.

By doing this, the login view redirects users to the URL that they were trying to access after they successfully log in. Remember that you added a hidden input in the form of your login template for this purpose.

You can also define a section variable. You will use this variable to track the site's section that the user is browsing. Multiple views may correspond to the same section. This is a simple way to define the section that each view corresponds to.

Next, you will 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 = [
    # ...
    path('', views.dashboard, name='dashboard'),
]

Edit the settings.py file of your project and add the following code to it:

LOGIN_REDIRECT_URL = 'dashboard'
LOGIN_URL = 'login'
LOGOUT_URL = 'logout'

The settings defined in the preceding code are as follows:

  • LOGIN_REDIRECT_URL: Tells Django which URL to redirect the user to after a successful login if no next parameter is present in the request
  • LOGIN_URL: The URL to redirect the user to log in (for example, views using the login_required decorator)
  • LOGOUT_URL: The URL to redirect the user to log out

You are using the names of the URL patterns that you previously defined using the name attribute of the path() function. Hardcoded URLs instead of URL names can also be used for these settings.

Let's summarize what you have done so far:

  • You have added the built-in Django authentication log in and log out views to your project
  • You have created custom templates for both views and defined a simple dashboard view to redirect users after they log in
  • Finally, you have configured your settings for Django to use these URLs by default

Now, you will add log in and log out links to your base template to put everything together. In order to do this, you have to determine whether the current user is logged in or not in order to display the appropriate link for each case. The current user is set in the HttpRequest 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 whether the current user is authenticated is by accessing the read-only attribute is_authenticated.

Edit your base.html template and modify the <div> element with a header ID, 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 in the preceding code, you only display the site's menu to authenticated users. You 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. You 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 login page. Enter a valid username and password and click on the Log-in button. You should see the following output:

Figure 4.5: The dashboard page

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 on the right side of the header. Click on the Logout link. You should see the following page:

Figure 4.6: The logged out page

In the page from the preceding screenshot, you can see that the user is logged out, and, therefore, the menu of the website is not being displayed anymore. Now, the link on the right side of the header shows Log-in.

If you see the logged out page of the Django administration site instead of your own logged 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.

Changing password views

You also need your users to be able to change their password after they log in to your site. You will integrate Django authentication views for a password change.

Open the urls.py file of the account application and add the following URL patterns to it:

# change password urls
path('password_change/',
     auth_views.PasswordChangeView.as_view(),
     name='password_change'),
path('password_change/done/',
      auth_views.PasswordChangeDoneView.as_view(),
      name='password_change_done'),

The PasswordChangeView view will handle the form to change the password, and the PasswordChangeDoneView view will display a success message after the user has successfully changed their 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 your password{% endblock %}
{% block content %}
  <h1>Change your password</h1>
  <p>Use the form below to change your password.</p>
  <form method="post">
    {{ form.as_p }}
    <p><input type="submit" value="Change"></p>
    {% csrf_token %}
  </form>
{% endblock %}

The password_change_form.html 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 %}

The password_change_done.html 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 you are 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:

Figure 4.7: The change password form

Fill in the form with your current password and your new password, and click on the CHANGE button. You will see the following success page:

Figure 4.8: The successful password change page

Log out and log in again using your new password to verify that everything works as expected.

Resetting password views

Add the following URL patterns for password restoration to the urls.py file of the account application:

# reset password urls
path('password_reset/',
     auth_views.PasswordResetView.as_view(),
     name='password_reset'),
path('password_reset/done/',
     auth_views.PasswordResetDoneView.as_view(),
     name='password_reset_done'),
path('reset/<uidb64>/<token>/',
     auth_views.PasswordResetConfirmView.as_view(),
     name='password_reset_confirm'),
path('reset/done/',
     auth_views.PasswordResetCompleteView.as_view(),
     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 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 }}

The password_reset_email.html template will be used to render the email sent to users to reset their password. It includes a reset token that is generated by the view.

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 in the same directory 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 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 %}

In this template, you check whether the link for resetting the password is valid by checking the validlink variable. The view PasswordResetConfirmView checks the validity of the token provided in the URL and passes the validlink variable to the template. If the link is valid, you display the user password reset form. Users can only set a new password if they have a valid reset password link.

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 on the Forgotten your password? link. You should see the following page:

Figure 4.9: The restore password form

At this point, you need to add a Simple Mail Transfer Protocol (SMTP) configuration to the settings.py file of your project so that Django is able to send emails. You learned how to add email settings to your project in Chapter 2, Enhancing Your Blog with Advanced Features. However, during development, you can configure Django to write emails to the standard output instead of sending them through an SMTP server. Django provides an email backend to write emails 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 emails.

Return to your browser, enter the email address of an existing user, and click on the SEND E-MAIL button. You should see the following page:

Figure 4.10: The reset password email sent page

Take a look at the console where you are running the development server. You will see the generated email, as follows:

Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Password reset on 127.0.0.1:8000
From: webmaster@localhost
To: [email protected]
Date: Fri, 3 Jan 2020 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/reset/MQ/45f-9c3f30caafd523055fcc/
Your username, in case you've forgotten: zenx

The email is rendered using the password_reset_email.html template that you created earlier. The URL to reset the password includes a token that was generated dynamically by Django.

Copy the URL and open it in your browser. You should see the following page:

Figure 4.11: The reset password form

The page to set a new password uses the password_reset_confirm.html template. Fill in a new password and click on the CHANGE MY PASSWORD button. Django creates a new hashed password and saves it in the database. You will see the following success page:

Figure 4.12: The successful password reset page

Now you can log back into 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 stating that the token is invalid.

You have now integrated the views of the Django authentication framework into your project. These views are suitable for most cases. However, you can create your own views if you need a different behavior.

Django also provides the authentication URL patterns that you just created. You can comment out the authentication URL patterns that you added to the urls.py file of the account application and include django.contrib.auth.urls instead, as follows:

from django.urls import path, include
# ...
urlpatterns = [
    # ...
    path('', include('django.contrib.auth.urls')),
]

You can see the authentication URL patterns included at https://github.com/django/django/blob/stable/3.0.x/django/contrib/auth/urls.py.

User registration and user profiles

Existing users can now log in, log out, change their password, and reset their password. Now, you will need to build a view to allow visitors to create a user account.

User registration

Let's create a simple view to allow user registration on your website. Initially, you have to create a form to let the user enter a username, their real name, and a password.

Edit the forms.py file located inside the account application directory and add the following code to it:

from django.contrib.auth.models import User
class UserRegistrationForm(forms.ModelForm):
    password = forms.CharField(label='Password',
                               widget=forms.PasswordInput)
    password2 = forms.CharField(label='Repeat password',
                                widget=forms.PasswordInput)
    class Meta:
        model = User
        fields = ('username', 'first_name', 'email')
    def clean_password2(self):
        cd = self.cleaned_data
        if cd['password'] != cd['password2']:
            raise forms.ValidationError('Passwords don't match.')
        return cd['password2']

You have created a model form for the user model. In your form, you include only the username, first_name, and email fields of the model. These fields will be validated based on their corresponding model fields. For example, if the user chooses a username that already exists, they will get a validation error because username is a field defined with unique=True.

You have added two additional fields—password and password2—for users to set their password and confirm it. You have defined a clean_password2() method to check the second password against the first one and not let the form validate if the passwords don't match. This check is done when you validate the form by calling its is_valid() method. You can provide a clean_<fieldname>() method to any of your form fields in order to clean the value or raise form validation errors for a specific field. Forms also include a general clean() method to validate the entire form, which is useful to validate fields that depend on each other. In this case, you use the field-specific clean_password2() validation instead of overriding the clean() method of the form. This avoids overriding other field-specific checks that the ModelForm gets from the restrictions set in the model (for example, validating that the username is unique).

Django also provides a UserCreationForm form that you can use, which resides in django.contrib.auth.forms and is very similar to the one you have created.

Edit the views.py file of the account application and add the following code to it:

from .forms import LoginForm, UserRegistrationForm
def register(request):
    if request.method == 'POST':
        user_form = UserRegistrationForm(request.POST)
        if user_form.is_valid():
            # Create a new user object but avoid saving it yet
            new_user = user_form.save(commit=False)
            # Set the chosen password
            new_user.set_password(
                user_form.cleaned_data['password'])
            # Save the User object
            new_user.save()
            return render(request,
                          'account/register_done.html',
                          {'new_user': new_user})
    else:
        user_form = UserRegistrationForm()
    return render(request,
                  'account/register.html',
                  {'user_form': user_form})

The view for creating user accounts is quite simple. For security reasons, instead of saving the raw password entered by the user, you use the set_password() method of the user model that handles hashing.

Now, edit the urls.py file of your account application and add the following URL pattern:

path('register/', views.register, name='register'),

Finally, create a new template in the account/ template directory, name it register.html, and make it look as follows:

{% extends "base.html" %}
{% block title %}Create an account{% endblock %}
{% block content %}
  <h1>Create an account</h1>
  <p>Please, sign up using the following form:</p>
  <form method="post">
    {{ user_form.as_p }}
    {% csrf_token %}
    <p><input type="submit" value="Create my account"></p>
  </form>
{% endblock %}

Add a template file in the same directory and name it register_done.html. Add the following code to it:

{% extends "base.html" %}
{% block title %}Welcome{% endblock %}
{% block content %}
  <h1>Welcome {{ new_user.first_name }}!</h1>
  <p>Your account has been successfully created. Now you can <a href="{% url "login" %}">log in</a>.</p>
{% endblock %}

Now open http://127.0.0.1:8000/account/register/ in your browser. You will see the registration page you have created:

Figure 4.13: The account creation form

Fill in the details for a new user and click on the CREATE MY ACCOUNT button. If all fields are valid, the user will be created, and you will get the following success message:

Figure 4.14: The account is successfully created page

Click on the log in link and enter your username and password to verify that you can access your account.

You can also add a link to registration in your login template. Edit the registration/login.html template and find the following line:

<p>Please, use the following form to log-in:</p>

Replace it with the following:

<p>Please, use the following form to log-in. If you don't have an account <a href="{% url "register" %}">register here</a></p>

You have made the signup page accessible from the login page.

Extending the user model

When you have to deal with user accounts, you will find that the user model of the Django authentication framework is suitable for common cases. However, the user model comes with very basic fields. You may wish to extend it to include additional data. The best way to do this is by creating a profile model that contains all additional fields and a one-to-one relationship with the Django User model. A one-to-one relationship is similar to a ForeignKey field with the parameter unique=True. The reverse side of the relationship is an implicit one-to-one relationship with the related model instead of a manager for multiple elements. From each side of the relationship, you retrieve a single related object.

Edit the models.py file of your account application and add the following code to it:

from django.db import models
from django.conf import settings
class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL,
                                on_delete=models.CASCADE)
    date_of_birth = models.DateField(blank=True, null=True)
    photo = models.ImageField(upload_to='users/%Y/%m/%d/',
                              blank=True)
    def __str__(self):
        return f'Profile for user {self.user.username}'

In order to keep your code generic, use the get_user_model() method to retrieve the user model and the AUTH_USER_MODEL setting to refer to it when defining a model's relationship with the user model, instead of referring to the auth user model directly. You can read more information about this at https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#django.contrib.auth.get_user_model.

The user one-to-one field allows you to associate profiles with users. You use CASCADE for the on_delete parameter so that its related profile also gets deleted when a user is deleted. The photo field is an ImageField field. You will need to install the Pillow library to handle images. Install Pillow by running the following command in your shell:

pip install Pillow==7.0.0

To enable Django to serve media files uploaded by users with the development server, add the following settings to the settings.py file of your project:

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

MEDIA_URL is the base URL used to serve the media files uploaded by users, and MEDIA_ROOT is the local path where they reside. You build the path dynamically relative to your project path to make your code more generic.

Now, edit the main urls.py file of the bookmarks project and modify the code, as follows:

from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
    path('admin/', admin.site.urls),
    path('account/', include('account.urls')),
]
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL,
                          document_root=settings.MEDIA_ROOT)

In this way, the Django development server will be in charge of serving the media files during development (that is when the DEBUG setting is set to True).

The static() helper function is suitable for development, but not for production use. Django is very inefficient at serving static files. Never serve your static files with Django in a production environment. You will learn how to serve static files in a production environment in Chapter 14, Going Live.

Open the shell and run the following command to create the database migration for the new model:

python manage.py makemigrations

You will get the following output:

Migrations for 'account':
  account/migrations/0001_initial.py
    - Create model Profile

Next, sync the database with the following command:

python manage.py migrate

You will see an output that includes the following line:

Applying account.0001_initial... OK

Edit the admin.py file of the account application and register the Profile model in the administration site, like this:

from django.contrib import admin
from .models import Profile
@admin.register(Profile)
class ProfileAdmin(admin.ModelAdmin):
    list_display = ['user', 'date_of_birth', 'photo']

Run the development server using the python manage.py runserver command and open http://127.0.0.1:8000/admin/ in your browser. Now you should be able to see the Profile model in the administration site of your project, as follows:

Figure 4.15: The ACCOUNT block of the administration site index page

Next, you will let users edit their profile on the website. Add the following import and model forms to the forms.py file of the account application:

from .models import Profile
class UserEditForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ('first_name', 'last_name', 'email')
class ProfileEditForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ('date_of_birth', 'photo')

These forms are as follows:

  • UserEditForm: This will allow users to edit their first name, last name, and email, which are attributes of the built-in Django user model.
  • ProfileEditForm: This will allow users to edit the profile data that you save in the custom Profile model. Users will be able to edit their date of birth and upload a picture for their profile.

Edit the views.py file of the account application and import the Profile model, like this:

from .models import Profile

Then, add the following lines to the register view below new_user.save():

# Create the user profile
Profile.objects.create(user=new_user)

When users register on your site, you will create an empty profile associated with them. You should create a Profile object manually using the administration site for the users that you created before.

Now, you will let users edit their profile. Add the following code to the same file:

from .forms import LoginForm, UserRegistrationForm, 
                   UserEditForm, ProfileEditForm
@login_required
def edit(request):
    if request.method == 'POST':
        user_form = UserEditForm(instance=request.user,
                                 data=request.POST)
        profile_form = ProfileEditForm(
                                    instance=request.user.profile,
                                    data=request.POST,
                                    files=request.FILES)
        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
    else:
        user_form = UserEditForm(instance=request.user)
        profile_form = ProfileEditForm(
                                    instance=request.user.profile)
    return render(request,
                  'account/edit.html',
                  {'user_form': user_form,
                   'profile_form': profile_form})

You use the login_required decorator because users have to be authenticated to edit their profile. In this case, you are using two model forms: UserEditForm to store the data of the built-in user model and ProfileEditForm to store the additional profile data in the custom Profile model. To validate the submitted data, you execute the is_valid() method of both forms. If both forms contain valid data, you save both forms, calling the save() method to update the corresponding objects in the database.

Add the following URL pattern to the urls.py file of the account application:

path('edit/', views.edit, name='edit'),

Finally, create a template for this view in templates/account/ and name it edit.html. Add the following code to it:

{% extends "base.html" %}
{% block title %}Edit your account{% endblock %}
{% block content %}
  <h1>Edit your account</h1>
  <p>You can edit your account using the following form:</p>
  <form method="post" enctype="multipart/form-data">
    {{ user_form.as_p }}
    {{ profile_form.as_p }}
    {% csrf_token %}
    <p><input type="submit" value="Save changes"></p>
  </form>
{% endblock %}

In the preceding code, you include enctype="multipart/form-data" in your form to enable file uploads. You use an HTML form to submit both the user_form and the profile_form forms.

Register a new user from the URL http://127.0.0.1:8000/account/register/ and open http://127.0.0.1:8000/account/edit/. You should see the following page:

Figure 4.16: The profile edit form

You can also edit the dashboard page and include links to the edit profile and change password pages. Open the account/dashboard.html template and find the following line:

<p>Welcome to your dashboard.</p>

Replace the preceding line with the following one:

<p>Welcome to your dashboard. You can <a href="{% url "edit" %}">edit your profile</a> or <a href="{% url "password_change" %}">change your password</a>.</p>

Users can now access the form to edit their profile from their dashboard. Open http://127.0.0.1:8000/account/ in your browser and test the new link to edit a user's profile:

Figure 4.17: Dashboard page content, including links to edit a profile and change a password

Using a custom user model

Django also offers a way to substitute the whole user model with your own custom model. Your user class should inherit from Django's AbstractUser class, which provides the full implementation of the default user as an abstract model. You can read more about this method at https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#substituting-a-custom-user-model.

Using a custom user model will give you more flexibility, but it might also result in more difficult integration with pluggable applications that interact with Django's auth user model.

Using the messages framework

When allowing users to interact with your platform, there are many cases where you might want to inform them about the result of their actions. Django has a built-in messages framework that allows you to display one-time notifications to your users.

The messages framework is located at django.contrib.messages and is included in the default INSTALLED_APPS list of the settings.py file when you create new projects using python manage.py startproject. You will note that your settings file contains a middleware named django.contrib.messages.middleware.MessageMiddleware in the MIDDLEWARE settings.

The messages framework provides a simple way to add messages to users. Messages are stored in a cookie by default (falling back to session storage), and they are displayed in the next request from the user. You can use the messages framework in your views by importing the messages module and adding new messages with simple shortcuts, as follows:

from django.contrib import messages
messages.error(request, 'Something went wrong')

You can create new messages using the add_message() method or any of the following shortcut methods:

  • success(): Success messages to be displayed after an action has been successful
  • info(): Informational messages
  • warning(): Something has not yet failed but may fail imminently
  • error(): An action was not successful or something failed
  • debug(): Debug messages that will be removed or ignored in a production environment

Let's add messages to your platform. Since the messages framework applies globally to the project, you can display messages for the user in your base template. Open the base.html template of the account application and add the following code between the <div> element with the header ID and the <div> element with the content ID:

{% if messages %}
  <ul class="messages">
    {% for message in messages %}
      <li class="{{ message.tags }}">
        {{ message|safe }}
        <a href="#" class="close">x</a>
      </li>
    {% endfor %}
  </ul>
{% endif %}

The messages framework includes the context processor django.contrib.messages.context_processors.messages, which adds a messages variable to the request context. You can find it in the context_processors list of the TEMPLATES setting of your project. You can use the messages variable in your templates to display all existing messages to the user.

A context processor is a Python function that takes the request object as an argument and returns a dictionary that gets added to the request context. You will learn how to create your own context processors in Chapter 7, Building an Online Shop.

Let's modify your edit view to use the messages framework. Edit the views.py file of the account application, import messages, and make the edit view look as follows:

from django.contrib import messages
@login_required
def edit(request):
    if request.method == 'POST':
        # ...
        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
            messages.success(request, 'Profile updated '
                                      'successfully')
        else:
            messages.error(request, 'Error updating your profile')
    else:
        user_form = UserEditForm(instance=request.user)
        # ...

You add a success message when the user successfully updates their profile. If any of the forms contain invalid data, you add an error message instead.

Open http://127.0.0.1:8000/account/edit/ in your browser and edit your profile. When the profile is successfully updated, you should see the following message:

Figure 4.18: The successful edit profile message

When data is not valid, for example, if there is an incorrectly formatted date for the date of birth field, you should see the following message:

Figure 4.19: The error updating profile message

You can learn more about the messages framework at https://docs.djangoproject.com/en/3.0/ref/contrib/messages/.

Building a custom authentication backend

Django allows you to authenticate against different sources. The AUTHENTICATION_BACKENDS setting includes the list of authentication backends for your project. By default, this setting is set as follows:

['django.contrib.auth.backends.ModelBackend']

The default ModelBackend authenticates users against the database using the user model of django.contrib.auth. This will suit most of your projects. However, you can create custom backends to authenticate your user against other sources, such as a Lightweight Directory Access Protocol (LDAP) directory or any other system.

You can read more information about customizing authentication at https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#other-authentication-sources.

Whenever you use the authenticate() function of django.contrib.auth, Django tries to authenticate the user against each of the backends defined in AUTHENTICATION_BACKENDS one by one, until one of them successfully authenticates the user. Only if all of the backends fail to authenticate will the user not be authenticated into your site.

Django provides a simple way to define your own authentication backends. An authentication backend is a class that provides the following two methods:

  • authenticate(): It takes the request object and user credentials as parameters. It has to return a user object that matches those credentials if the credentials are valid, or None otherwise. The request parameter is an HttpRequest object, or None if it's not provided to authenticate().
  • get_user(): This takes a user ID parameter and has to return a user object.

Creating a custom authentication backend is as simple as writing a Python class that implements both methods. Let's create an authentication backend to let users authenticate in your site using their email address instead of their username.

Create a new file inside your account application directory and name it authentication.py. Add the following code to it:

from django.contrib.auth.models import User
class EmailAuthBackend(object):
    """
    Authenticate using an e-mail address.
    """
    def authenticate(self, request, username=None, password=None):
        try:
            user = User.objects.get(email=username)
            if user.check_password(password):
                return user
            return None
        except User.DoesNotExist:
            return None
    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

The preceding code is a simple authentication backend. The authenticate() method receives a request object and the username and password optional parameters. You could use different parameters, but you use username and password to make your backend work with the authentication framework views right away. The preceding code works as follows:

  • authenticate(): You try to retrieve a user with the given email address and check the password using the built-in check_password() method of the user model. This method handles the password hashing to compare the given password with the password stored in the database.
  • get_user(): You get a user through the ID provided in the user_id parameter. Django uses the backend that authenticated the user to retrieve the User object for the duration of the user session.

Edit the settings.py file of your project and add the following setting:

AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.ModelBackend',
    'account.authentication.EmailAuthBackend',
]

In the preceding setting, you keep the default ModelBackend that is used to authenticate with the username and password and include your own email-based authentication backend.

Now open http://127.0.0.1:8000/account/login/ in your browser. Remember that Django will try to authenticate the user against each of the backends, so now you should be able to log in seamlessly using your username or email account. User credentials will be checked using the ModelBackend authentication backend, and if no user is returned, the credentials will be checked using your custom EmailAuthBackend backend.

The order of the backends listed in the AUTHENTICATION_BACKENDS setting matters. If the same credentials are valid for multiple backends, Django will stop at the first backend that successfully authenticates the user.

Adding social authentication to your site

You might also want to add social authentication to your site using services such as Facebook, Twitter, or Google. Python Social Auth is a Python module that simplifies the process of adding social authentication to your website. Using this module, you can let your users log in to your website using their accounts from other services.

Social authentication is a widely used feature that makes the authentication process easier for users. You can find the code of this module at https://github.com/python-social-auth.

This module comes with authentication backends for different Python frameworks, including Django. To install the Django package via pip, open the console and run the following command:

pip install social-auth-app-django==3.1.0

Then add social_django to the INSTALLED_APPS setting in the settings.py file of your project:

INSTALLED_APPS = [
    #...
    'social_django',
]

This is the default application to add Python Social Auth to Django projects. Now run the following command to sync Python Social Auth models with your database:

python manage.py migrate

You should see that the migrations for the default application are applied as follows:

Applying social_django.0001_initial... OK
Applying social_django.0002_add_related_name... OK
...
Applying social_django.0008_partial_timestamp... OK

Python Social Auth includes backends for multiple services. You can see a list of all backends at https://python-social-auth.readthedocs.io/en/latest/backends/index.html#supported-backends.

Let's include authentication backends for Facebook, Twitter, and Google.

You will need to add social login URL patterns to your project. Open the main urls.py file of the bookmarks project and include the social_django URL patterns as follows:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('account/', include('account.urls')),
    path('social-auth/',
         include('social_django.urls', namespace='social')),
]

Several social services will not allow redirecting users to 127.0.0.1 or localhost after a successful authentication; they expect a domain name. In order to make social authentication work, you will need a domain. To fix this on Linux or macOS, edit your /etc/hosts file and add the following line to it:

127.0.0.1 mysite.com

This will tell your computer to point the mysite.com hostname to your own machine. If you are using Windows, your hosts file is located at C:WindowsSystem32Driversetchosts.

To verify that your hostname association worked, start the development server with python manage.py runserver and open http://mysite.com:8000/account/login/ in your browser. You will see the following error:

Figure 4.20: The invalid host header message

Django controls the hosts that are able to serve your application using the ALLOWED_HOSTS setting. This is a security measure to prevent HTTP host header attacks. Django will only allow the hosts included in this list to serve the application. You can learn more about the ALLOWED_HOSTS setting at https://docs.djangoproject.com/en/3.0/ref/settings/#allowed-hosts.

Edit the settings.py file of your project and edit the ALLOWED_HOSTS setting as follows:

ALLOWED_HOSTS = ['mysite.com', 'localhost', '127.0.0.1']

Besides the mysite.com host, you explicitly include localhost and 127.0.0.1. This is in order to be able to access the site through localhost, which is the default Django behavior when DEBUG is True and ALLOWED_HOSTS is empty. Now you should be able to open http://mysite.com:8000/account/login/ in your browser.

Running the development server through HTTPS

Some of the social authentication methods you are going to use require an HTTPS connection. The Transport Layer Security (TLS) protocol is the standard for serving websites through a secure connection. The TLS predecessor is the Secure Sockets Layer (SSL).

Although SSL is now deprecated, in multiple libraries and online documentation you will find references to both the terms TLS and SSL. The Django development server is not able to serve your site through HTTPS, since that is not its intended use. In order to test the social authentication functionality serving your site through HTTPS, you are going to use the RunServerPlus extension of the package Django Extensions. Django Extensions is a third-party collection of custom extensions for Django. Please note that this is never the method you should use to serve your site in a real environment; this is a development server.

Use the following command to install Django Extensions:

pip install django-extensions==2.2.5

Now you need to install Werkzeug, which contains a debugger layer required by the RunServerPlus extension. Use the following command to install it:

pip install werkzeug==0.16.0

Finally, use the following command to install pyOpenSSL, which is required to use the SSL/TLS functionality of RunServerPlus:

pip install pyOpenSSL==19.0.0

Edit the settings.py file of your project and add Django Extensions to the INSTALLED_APPS setting, as follows:

INSTALLED_APPS = [
    # ...
    'django_extensions',
]

Use the management command runserver_plus provided by Django Extensions to run the development server, as follows:

python manage.py runserver_plus --cert-file cert.crt

You provide a file name to the runserver_plus command for the SSL/TLS certificate. Django Extensions will generate a key and certificate automatically.

Open https://mysite.com:8000/account/login/ in your browser. Now you are accessing your site through HTTPS. Your browser might show a security warning because you are using a self-generated certificate. If this is the case, access the advanced information displayed by your browser and accept the self-signed certificate so that your browser trusts the certificate.

You will see that the URL starts with https:// and a security icon that indicates that the connection is secure.

4.21 The URL with the secured connection icon

You can now serve your site through HTTPS during development in order to test social authentication with Facebook, Twitter, and Google.

Authentication using Facebook

To use Facebook authentication to log in to your site, add the following line to the AUTHENTICATION_BACKENDS setting in the settings.py file of your project:

'social_core.backends.facebook.FacebookOAuth2',

You will need a Facebook developer account and you will need to create a new Facebook application. Open https://developers.facebook.com/apps/ in your browser. After creating a Facebook developer account, you will see a site with the following header:

Figure 4.22: The Facebook developer portal menu

Under the menu item My Apps, click on the button Create App. You will see the following form to create a new application ID:

Figure 4.23: The Facebook create app ID form

Enter Bookmarks as the Display Name, add a contact email address, and click on Create App ID. You will see a dashboard for your new application that displays different features you can set up for it. Look for the following Facebook Login box and click on Set Up:

Figure 4.24: The Facebook login product block

You will be asked to choose the platform, as follows:

Figure 4.25: Platform selection for Facebook login

Select the Web platform. You will see the following form:

Figure 4.26: Web platform configuration for Facebook login

Enter http://mysite.com:8000/ as your Site URL and click on the Save button. You can skip the rest of the quickstart process. In the left-hand menu, click on Settings and then on Basic. You will see something similar to the following:

Figure 4.27: Application details for the Facebook application

Copy the App ID and App Secret keys and add them to the settings.py file of your project, as follows:

SOCIAL_AUTH_FACEBOOK_KEY = 'XXX' # Facebook App ID
SOCIAL_AUTH_FACEBOOK_SECRET = 'XXX' # Facebook App Secret

Optionally, you can define a SOCIAL_AUTH_FACEBOOK_SCOPE setting with the extra permissions you want to ask Facebook users for:

SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']

Now, go back to Facebook and click on Settings. You will see a form with multiple settings for your application. Add mysite.com under App Domains, as follows:

Figure 4.28: Allowed domains for the Facebook application

Click on Save Changes. Then, in the left-hand menu under Products, click on Facebook Login and then Settings, as shown here:

Figure 4.29: The Facebook login menu

Ensure that only the following settings are active:

  • Client OAuth Login
  • Web OAuth Login
  • Enforce HTTPS
  • Embedded Browser OAuth Login

Enter http://mysite.com:8000/social-auth/complete/facebook/ under Valid OAuth Redirect URIs. The selection should look like this:

Figure 4.30: Client OAuth settings for Facebook login

Open the registration/login.html template of your account application and append the following code at the bottom of the content block:

<div class="social">
  <ul>
    <li class="facebook">
      <a href="{% url "social:begin" "facebook" %}">Sign in with Facebook</a>
    </li>
  </ul>
</div>

Open https://mysite.com:8000/account/login/ in your browser. Now, the login page will look as follows:

Figure 4.31: The login page including the button for Facebook authentication

Click on the Sign in with Facebook button. You will be redirected to Facebook, and you will see a modal dialog asking for your permission to let the Bookmarks application access your public Facebook profile:

Figure 4.32: The modal dialog to grant application permissions

Click on the Continue as button. You will be logged in and redirected to the dashboard page of your site. Remember that you have set this URL in the LOGIN_REDIRECT_URL setting. As you can see, adding social authentication to your site is pretty straightforward.

Authentication using Twitter

For social authentication using Twitter, add the following line to the AUTHENTICATION_BACKENDS setting in the settings.py file of your project:

'social_core.backends.twitter.TwitterOAuth',

You will need to create a new application in your Twitter account. Open https://developer.twitter.com/en/apps/create in your browser. You will be asked several questions to create a Twitter developer account if you haven't done that yet. Once you have a developer account, when creating a new application, you will see the following form:

Figure 4.33: Twitter application configuration

Enter the details of your application, including the following settings:

  • Website: https://mysite.com:8000/
  • Callback URL: https://mysite.com:8000/social-auth/complete/twitter/

Make sure that you activate Enable Sign in with Twitter. Then, click on Create. You will see the application details. Click on the Keys and tokens tab. You should see the following information:

Figure 4.34: Twitter application API keys

Copy the API key and API secret key into the following settings in the settings.py file of your project:

SOCIAL_AUTH_TWITTER_KEY = 'XXX' # Twitter API Key
SOCIAL_AUTH_TWITTER_SECRET = 'XXX' # Twitter API Secret

Now edit the registration/login.html template and add the following code to the <ul> element:

<li class="twitter">
  <a href="{% url "social:begin" "twitter" %}">Login with Twitter</a>
</li>

Open https://mysite.com:8000/account/login/ in your browser and click on the Log in with Twitter link. You will be redirected to Twitter, and it will ask you to authorize the application as follows:

Figure 4.35: The modal dialog to grant application permissions

Click on the Authorize app button. You will be logged in and redirected to the dashboard page of your site.

Authentication using Google

Google offers social authentication using OAuth2. You can read about Google's OAuth2 implementation at https://developers.google.com/identity/protocols/OAuth2.

To implement authentication using Google, add the following line to the AUTHENTICATION_BACKENDS setting in the settings.py file of your project:

'social_core.backends.google.GoogleOAuth2',

First, you will need to create an API key in your Google Developer Console. Open https://console.developers.google.com/apis/credentials in your browser. Click on Select a project and then on New project create a new project, as follows:

Figure 4.36: The Google project creation form

After the project is created, under Credentials click on CREATE CREDENTIALS and choose OAuth client ID, as follows:

Figure 4.37: Google API creation of API credentials

Google will ask you to configure the consent screen first:

Figure 4.38: The alert to configure the OAuth consent screen

The preceding page is the page that will be shown to users to give their consent to access your site with their Google account. Click on the Configure consent screen button. You will be redirected to the following screen:

Figure 4.39: User type selection in the Google OAuth consent screen setup

Choose External for User Type and click on the CREATE button. You will see the following screen:

Figure 4.40: Google OAuth consent screen setup

Fill in the form with the following information:

  • Application name: Enter Bookmarks
  • Authorised domains: Enter mysite.com

Click on the Save button. The consent screen for your application will be configured and you will see the details of your application consent screen, as follows:

Figure 4.41: Google OAuth consent screen details

In the menu on the left sidebar, click on Credentials and click again on CREATE CREDENTIALS and then on OAuth client ID.

As the next step, enter the following information:

  • Application type: Select Web application
  • Name: Enter Bookmarks
  • Authorised redirect URIs: Add https://mysite.com:8000/social-auth/complete/google-oauth2/

The form should look like this:

Figure 4.42: The Google application creation form

Click on the Create button. You will get the Client ID and Client Secret keys. Add them to your settings.py file, like this:

SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'XXX' # Google Consumer Key
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'XXX' # Google Consumer Secret

In the left-hand menu of the Google Developers Console, under the APIs & Services section, click on the Library item. You will see a list that contains all Google APIs. Click on Google+ API and then click on the ENABLE button on the following page:

Figure 4.43: The Google+ API block

Edit the registration/login.html template and add the following code to the <ul> element:

<li class="google">
  <a href="{% url "social:begin" "google-oauth2" %}">Login with Google</a>
</li>

Open https://mysite.com:8000/account/login/ in your browser. The login page should now look as follows:

Figure 4.44: The login page including buttons for Twitter and Google authentication

Click on the Login with Google button. You will be logged in and redirected to the dashboard page of your website.

You have now added social authentication to your project. You can easily implement social authentication with other popular online services using Python Social Auth.

Summary

In this chapter, you learned how to build an authentication system into your site. You implemented all the necessary views for users to register, log in, log out, edit their password, and reset their password. You built a model for custom user profiles and you created a custom authentication backend to let users log in to your site using their email address. You also added social authentication to your site so that users can use their existing Facebook, Twitter, or Google account to log in.

In the next chapter, you will learn how to create an image bookmarking system, generate image thumbnails, and build AJAX views.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.20.156