Django REST Framework Tutorial : User Authentication

Welcome back to the 3rd series of my Django Rest framework tutorial. This post would be a continuation of my previous post, please click on any link below to view any post you missed

The code to this tutorial is hosted on Github

OUTLINE OF DJANGO REST FRAMEWORK SERIES

At the end of this post, you will learn the following

  • Set up user workflow from registration, email confirmation after registration, password recovery, login, and logout features.
  • Set up documentation with Coreapi  and swagger docs with drf-yasg

We will use token-based authentication for our API.

 

Setting the authentication

Open your settings.py file and update DEFAULT_PERMISSION_CLASSES

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ],
}

We  add the rest_framework.authtokenwhich generates the tokens on the server in our INSTALLED_APPS settings.

INSTALLED_APPS = [
    ...
    'rest_framework.authtoken',
]

Note: Make sure to run python manage.py migrate after changing your settings. The rest_framework.authtoken app provides a Django database migration.

Now start up the server, run python manage.py runserver and navigate to the Django admin at http://127.0.0.1:8000/admin/

We would install new extensions

  • django-Rest-Auth: provides API endpoints for user registration, login/logout, password change/reset, social auth, and more.
  • django-allauth: to enable the standard registration process. It handles user registration as well as social authentication., also good for email address verification.
  •  django2-rest-passwordreset :  This python package provides a simple password reset strategy for the Django rest framework, where users can request a password reset tokens via their registered e-mail address.
pip install django-rest-auth
pip install django2-rest-passwordreset
pip install django-allauth

Update your settings.py, add the following apps to INSTALLED_APPS

    "django.contrib.sites",
    # 3rd rest framework
    "rest_framework",
    "rest_framework.authtoken",

    # cors setting
    "corsheaders",

    # django reset password
    "django_rest_passwordreset",

    # django-allauth and django-rest-auth
    "rest_auth",
    "rest_auth.registration",
    "allauth",
    "allauth.account",
    "allauth.socialaccount",

 

Configure templates folder

Update your settings.py, add the following

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [os.path.join(BASE_DIR, "templates")],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                #existing code
            ],
        },
    },
]
Update views.py
from django.core.mail import EmailMultiAlternatives
from django.dispatch import receiver
from django.template.loader import render_to_string
from django.urls import reverse
from django_rest_passwordreset.signals import reset_password_token_created
from rest_framework import generics, permissions, status
from rest_framework.decorators import api_view
from rest_framework.response import Response




@receiver(reset_password_token_created)
def password_reset_token_created(sender, instance, reset_password_token, *args, **kwargs):
    """
    Handles password reset tokens
    When a token is created, an e-mail needs to be sent to the user
    :param sender: View Class that sent the signal
    :param instance: View Instance that sent the signal
    :param reset_password_token: Token Model Object
    :param args:
    :param kwargs:
    :return:
    """
    # send an e-mail to the user
    context = {
        'current_user': reset_password_token.user,
        'username': reset_password_token.user.username,
        'email': reset_password_token.user.email,
        'reset_password_url': "{}?token={}".format(
            instance.request.build_absolute_uri(reverse('password_reset:reset-password-confirm')),
            reset_password_token.key),
        'site_name': "Blog App"
        
    }

    # render email text
    email_html_message = render_to_string('account/email/user_reset_password.html', context)
    email_plaintext_message = render_to_string('account/email/user_reset_password.txt', context)

    msg = EmailMultiAlternatives(
        # title:
        "Password Reset for {title}".format(title="Blog API"),
        # message:
        email_plaintext_message,
        # from:
        "noreply@example.com",
        # to:
        [reset_password_token.user.email]
    )
    msg.attach_alternative(email_html_message, "text/html")
    msg.send()

@api_view()
def success_view(request):
    return Response("Email account has been activated")
  • success_view is used to provide the successful feedback message once a user clicks the activation link sent to his email after registering.
  • password_reset_token_created  handles the token request and verify (confirm) a token when users want to reset their password.

Run the following command on your terminal.

mkdir templates & cd templates
mkdir account & cd account
mkdir email
templates
|---account
      |--email
          |-email_confirmation_message.txt
          |- email_confirmation_subject.txt
          |- user_reset_password.html
          |- user_reset_password.txt

 

  •  email/user_reset_password.html and  email/user_reset_password.txtemail .Those templates will handle password reset, it contains the e-mail message sent to the user, as well as the password reset link (or token).  Within the templates, we can access the following context variables: current_user, username, email, reset_password_url.
{% load i18n %}{% autoescape off %}Hello from {{ site_name }}!

You're receiving this e-mail because you or someone else has requested a password for your user account.
It can be safely ignored if you did not request a password reset. Click the link below to reset your password.{% endblocktrans %}

{{ reset_password_url }}

{% if username %}{% blocktrans %}In case you forgot, your username is {{ username }}.{% endblocktrans %}

{% endif %}Thank you for using {{ site_name }}!
{{ site_domain }}{% endblocktrans %}
{% endautoescape %}
  •  email/email_confirmation_message.txt This template will handle email confirmation after user registration.  If you want to customize it and learn more about it, check out django-alluth  source code on Github

 

{% load account %}{% user_display user as user_display %}{% load i18n %}{% autoescape off %}{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Hello from {{ site_name }}!

You're receiving this e-mail because user {{ user_display }} has given your e-mail address to register an account on {{ site_domain }}.

To confirm this is correct, go to {{ activate_url }}
{% endblocktrans %}
{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Thank you from {{ site_name }}!
{{ site_domain }}{% endblocktrans %}
{% endautoescape %}

Update urls.py

Open your blog_project/urls.py and add the following

from allauth.account.views import confirm_email
from django.conf.urls import url
from django.contrib import admin
from django.urls import include, path
from blog_api import views
from rest_auth.views import PasswordResetConfirmView

 urlpatterns = [
    path("admin/", admin.site.urls), 
    path("api-auth/", include("rest_framework.urls")),
    path("api/v1/rest-auth/", include("rest_auth.urls")),
    path("api/v1/rest-auth/registration/", include("rest_auth.registration.urls")),
    url(
        r"^api/v1/accounts-rest/registration/account-confirm-email/(?P<key>.+)/$",
        confirm_email,
        name="account_confirm_email",
    ),
    url(
        r"^api/v1/registration/complete/$", views.success_view, name="account_confirm_complete"
    ),
    url(
        r"^api/v1/password_reset/",
        include("django_rest_passwordreset.urls", namespace="password_reset"),
    ),
]
Set up Email Backend

Update your settings.py, add the following

EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

If you want to use an SMTP service, you can add this

EMAIL_HOST = "smtp.sendgrid.net"
EMAIL_HOST_USER = "apikey"
EMAIL_HOST_PASSWORD = "sendgrid_password"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
Configure django-allauth

Update your settings.py, add the following at the bottom

ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = "email"
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_CONFIRM_EMAIL_ON_GET = True
ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = "account_confirm_complete"
ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = "account_confirm_complete"
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 5
ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT = 86400  # 1 day in seconds
OLD_PASSWORD_FIELD_ENABLED = True
ACCOUNT_LOGIN_ON_PASSWORD_RESET = True
ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True

For more information about django-allauth configurations, read here

Run python manage.py migrate to make new migrations after changing your settings.

Run python manage.py runserverto start the server.

We have successfully set up our user flow.

 

Schemas and Documentation

A schema is a readable document that outlines all available API endpoints, URLs, and the HTTP verbs (GET, POST, PUT, DELETE, etc.) they support.

pip install coreapi pyyaml

Django REST Framework has built-in support for Core API. We add it to our project-level blog_project/urls.py

 

#Existing code view
from rest_framework.documentation import include_docs_urls
from rest_framework.schemas import get_schema_view


API_TITLE = "Blog API"  #
API_DESCRIPTION = "A Web API for creating and editing blog posts."

coreapi_schema_view = get_schema_view(title=API_TITLE)

 urlpatterns = [ 
     #existing code view
    path('docs/', include_docs_urls(title=API_TITLE,description=API_DESCRIPTION)),
    path("schema/", coreapi_schema_view),]

Start your server  run python manage.py runserver.and visit this URL http://127.0.0.1:8000/docs/

 

swagger

We also generate real Swagger/OpenAPI 2.0 specifications for our   Django Rest Framework API. We will use drf-yasg – Yet another Swagger generator

pip install -U drf-yasg

In blog_project/settings.py:

INSTALLED_APPS =[
               #existing code
              'drf_yasg',]

 

In blog_project/urls.py: add the following

#existing code
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi




schema_view = scheme(
    openapi.Info(
        title="Snippets API",
        default_version="v1",
        description="Test description",
        terms_of_service="https://www.google.com/policies/terms/",
        contact=openapi.Contact(email="contact@snippets.local"),
        license=openapi.License(name="BSD License"),
    ),
    public=True,
    permission_classes=(permissions.AllowAny,),
)



urlpatterns = [
   url(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
   url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
   url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
   ...
]

You can set your permission class to authenticated only, but for the purpose of this tutorial I am using permissions.AllowAny,

Our drf-yasg exposes 4 endpoints:

  • A JSON view of your API specification at /swagger.json
  • A YAML view of your API specification at /swagger.yaml
  • A swagger-ui view of your API specification at /swagger/
  • A ReDoc view of your API specification at /redoc/

Start your server  run python manage.py runserver.and visit any of this URL

  • http://127.0.0.1:8000/swagger/
  • http://127.0.0.1:8000/redoc/
  • http://127.0.0.1:8000/swagger.yaml
  • http://127.0.0.1:8000/swagger.json

 

In my next post, we’ll look at how we set up tests.

 

REFERENCES

 

Leave a Reply

Your email address will not be published. Required fields are marked *