Cách tùy chỉnh đăng nhập bằng email

Like bài viết:4 likes

Nếu đã làm việc với ứng dụng Django Auth của Django chúng ta biết rằng khi tạo trang đăng nhập sẽ mặc định dùng username/password để đăng nhập. Tuy nhiên trong thực tế chúng ta thấy thường các ứng dụng web dùng email để xác thực hơn là dùng username. Trong bài viết này chúng ta sẽ tìm hiểu làm sao để thực hiện điều này.

Khởi tạo project

 Project

Chúng ta bắt đầu cài Django và khởi tạo project:

pip install django==3.0.3
django-admin startproject myproject .

Lưu ýCó dấu chấm phía cuối dòng lệnh 

Users App

Tạo một ứng dụng để quản lý người dùng:

python manage.py startapp users

Thêm ứng dụng vừa tạo vào project:

myproject/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    'users',
]

 

Tùy chỉnh người dùng

Chúng ta sẽ tạo một CustomUser là một Model để tiện tùy chỉnh thêm các trường cho User sau này.

users/models.py

from django.db import models
from django.contrib.auth.models import AbstractUser


class CustomUser(AbstractUser):
    pass

Nếu bạn thắc mắc hoặc không rõ phần này có thể xem lại bài viết Django Authentication

Thay đổi settings để xác định lại User Model sẽ dùng :

myproject/settings.py

AUTH_USER_MODEL = 'users.CustomUser'

Bây giờ tiến hành chạy migrations:

python manage.py makemigrations users
python manage.py migrate

 

Home Page

Đầu tiên mình sẽ tạo trang home.

myproject/urls.py

from django.contrib import admin
from django.urls import path, include
from django.views.generic import TemplateView


urlpatterns = [
    path('admin/', admin.site.urls),
    path('', TemplateView.as_view(template_name='home.html'), name='home'),

    path('users/', include('django.contrib.auth.urls')),
]

Ở đây, mình đã thêm URLs của ứng dụng Django Auth vào để sử dụng.

Xác định thư mục template sẽ sử dụng:

myproject/settings.py

TEMPLATES = [
    {
        ...
        'DIRS': [
            os.path.join(BASE_DIR, 'templates'),
        ],
        ...
    },
]

Đây là file base.html

templates/base.html

<!doctype html>
{% load static %}
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>{% block title %}{% endblock title %}</title>
    <!-- Bootstrap core CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"\
      integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
</head>
<body>
    <header>
        <div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom box-shadow">
            <a href="{% url 'home' %}" class="navbar-brand my-0 mr-md-auto font-weight-normal">
                <img style="max-height: 50px" src="{% static 'img/logo-dark.png' %}" alt="">
            </a>
            <nav class="my-2 my-md-0 mr-md-3">
                {% if user.is_authenticated %}
                    <a class="p-2 text-dark">Hi! {{ user.username }}</a>
                    <a class="btn btn-danger btn-lg mr-2" href="{% url 'logout' %}">Đăng xuất</a>
                {% else %}
                <div class="d-flex">
                    <a class="btn btn-primary btn-lg" href="{% url 'login' %}">Đăng nhập</a>
                </div>
                {% endif %}
            </nav>
        </div>
    </header>
    <div class="container p-5">
        {% block main %}
        {% endblock main %}
    </div>
</body>
</html>

Tạo trang home:

templates/home.html

{% extends "base.html" %}
{% block title %}
Home Page
{% endblock title %}
{% block main %}
<div class="card bg-light text-center pt-5">
    <h1 class="display-4">djangobat</h1>
    <p class="lead text-muted">Cách tùy chỉnh đăng nhập bằng email</p>
</div>
{% endblock main %}

Chạy server

python manage.py runserver

Kết quả:

Đăng nhập bằng username

Bây giờ bạn sẽ tạo template cho trang login. Theo mặc định Django Auth sẽ tìm file tên là login.html trong thư mục templates/registration. Vì thế chúng ta tạo một file mới:

templates/registration/login.htm

{% extends "base.html" %}
{% load widget_tweaks %}

{% block title %}
Đăng nhập
{% endblock title %}

{% block main %}
<div class="text-center pt-5">
    <h1 class="display-4">Đăng nhập</h1>
    <p class="lead text-muted">Nhập thông tin phía dưới để tiến hành đăng nhập.</p>
</div>
<form action="{% url 'login' %}" method="post" style="max-width: 420px; margin: auto;" novalidate>
    {% csrf_token %}
    <div class="form-group">
        <label for="">Username or Email</label>
        {% render_field form.username class="form-control" placeholder="username or email" %}
    </div>
    <div class="form-group">
        <label for="">Password</label>
        {% render_field form.password class="form-control" placeholder="password" %}
    </div>
    <button class="btn btn-lg btn-primary" type="submit">Đăng nhập</button>

</form>
{% endblock main %}

Theo mặc định chúng ta có một biến form để sử dụng. Sử dụng django-widget-tweaks để thêm các class cho phần tử của form.

Tùy chỉnh settings

myproject/settings.py

LOGIN_REDIRECT_URL = 'home'
LOGOUT_REDIRECT_URL = 'login'
  • LOGIN_REDIRECT_URL: Xác định URL sẽ di chuyển tới khi đăng nhập thành công
  • LOGOUT_REDIRECT_URL: Xác định URL sẽ di chuyển tới khi đăng xuất thành công

Tiến hành tạo superuser :

python manage.py createsuperuser

Mở trang 127.0.0.1:8000/users/login/ có kết quả:

Tiến hành đăng nhập bằng usernam/password bạn vừa tạo bạn sẽ thấy :

Vậy bạn đă đăng nhập thành công bằng username. Nếu bạn thử đăng nhập bằng email bạn đã tạo trên sẽ có lỗi. Bây giờ chúng ta tùy chỉnh để có thể đăng nhập bằng email
 

Đăng nhập bằng email

Đầu tiên chúng ta phải biết làm thế nào để Django xác thực được thông tin người dùng đăng nhập. Django cung cấp một biến tên là AUTHENTICATION_BACKENDS ở trong file settings để xác định những class sẽ dùng để xác thực người dùng.

Theo mặc định, thì sẽ là:

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

Bạn sẽ không thấy biến này trong file setting tại vì Django ngầm xác định biến này. Bạn có thể xem class của ModeBackend tại đây

Bây giờ chúng ta sẽ tạo Authencation Backend cho email

users/authentication.py

from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend


User = get_user_model()

class EmailAuthBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            user = User.objects.get(email=username)
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            return None

Dùng hàm authenticate với các đối số theo mặc định như trên để lấy thông tin người dùng nhập. Rồi trả về người dùng có email trùng với trường username mà người dùng nhập.

Thêm vào file settings

myproject/settings.py

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

Bây giờ tiến hành đăng nhập lại bằng email sẽ thành công:

Trong trường hợp bạn không muốn sử dụng username để đăng nhập mà chỉ muốn dùng mỗi email thì bạn có thể thay đổi như sau:

myproject/settings.py

AUTHENTICATION_BACKENDS = [
    'users.authentication.EmailAuthBackend',
]

 

Xử lý lỗi

Bạn biết rằng trường email sẵn có của Django Auth là không phải unique, tức là nó không phải duy nhất cho mỗi user. Vậy nếu có 2 user đều dùng chung 1 email khi đó việc xác thực sẽ báo lỗi vì:

users/authentication.py

...
user = User.objects.get(email=username)
...

Chính câu lệnh này sẽ trả về 2 kết quả và báo lỗi.

Để giải quyết vấn đề này, chúng ta sẽ ghi đè lên trường email của User và để nó là unique. Thực hiện

users/models.py

from django.db import models
from django.contrib.auth.models import AbstractUser


class CustomUser(AbstractUser):
    email = models.EmailField(unique=True)

Tiến hành chạy migration cập nhật thay đổi :

python manage.py makemigrations users
python manage.py migrate

 

Kết luận

django-allauth cũng là một lựa chọn tốt khác để tùy chỉnh việc xác thực người dùng.

Code xem tại:  Github