Как реализовать регистрацию и логирование пользователей

Вступление

Начну пожалуй с вопроса о том, для чего вообще нужна система аутентификации пользователей ?
Иметь на своём сайте систему аутентификации важно, хотя бы потому что так вы будете расширять свою сеть клиентов/друзей/партнёров/читателей/заказчиков (подчеркнуть нужное)
Сразу оговорюсь, что система аутентификации, которую мы с тобой будем писать не основана на встроеном приложении django, django.contrib.auth . Это будет отдельное приложение с отдельной моделью к ней. Мы будем делать простую и понятную систему авторизации, основанную на анонимных сессионных ключах браузера.
Чтож, вернёмся к нашим баранам. Реализовать регистрацию и аутентификацию пользователей на django не сложно, хоть и требует спецефичных навыков. Для начала, нам потребуется проект, на котором мы вместе и реализуем аутентификацию пользователей.
Или ссылка на репозиторий на github
И если ты всё ещё не скипнул до готового решения, прошу следовать за мной.

Как работает аутентификация на сессионных ключах

Теперь, чтобы ты понял о чём я буду дальше писать, нужно объяснить как вообще сессии работают в django и как мы их можем использовать для создания пользователей. Сессия это маленький файл сгенерированный сервером, который хранит информацию о пользователе. Этот файл остаётся на сервере, дабы сервер знал с кем он общается. Под пользователем подразумевается браузер.
Процес общения пользователя и сервера
Браузер отправляет запрос и получает куки. Сервер общается с базой данных
Более подробно я рассказываю о сессиях в django в главе настройке видов в django

Настройка скриптов и jQuery

Допустим у вас есть собственный сайт на котором нужно реализовать систему аутентификации. Создать страницу входа и страницу регистрации. Ну или вы скачали мой шаблонный проект и по нему хотите понять как аутентификация пользователей работает на практике. Встаёт вопрос, с чего начать ? Предлагаю начать с написания ajax запросов.
Но перед этим создадим соответствующие директории для хранения скриптов и подключим эти скрипты к нашим шаблонам. Положим ты сейчас в папке приложения Auth. Тогда:

mkdir -p static/Auth/js;
cd static/Auth/js;
touch on_signup.js; touch on_signin.js;
	
В шаблоне signup.html вставь в самый конец перед {% endblock %}

			
<script src="{% static 'Auth/js/on_signup.js' %}"></script>
			
				
В шаблоне login.html проделай тоже самое только вставь

			
<script src="{% static 'Auth/js/on_signin.js' %}"></script>
			
				
Теперь когда всё необходимое было предварительно настроенно, можно переходить к сути или то как же сделать этот ajax запрос работать.

Ajax запрос для регистрации

Самое главное в самое начало. Код, ранее подключённого скрипта on_signup.js

const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;

function OnSignup(){
	var form_data = new FormData();
	form_data.append("csrfmiddlewaretoken", csrftoken);
	form_data.append("username", $("#username").val())
	form_data.append("email", $("#email").val())
	form_data.append("password",  $("#password").val())
	form_data.append("repassword", $("#repassword").val())
	$.ajax({
		type: "POST",
		url: "/signup/verify/",
		data: form_data,
		processData: false,
		contentType: false,
		headers: {'X-CSRFToken': csrftoken},
		mode: 'same-origin', // Do not send CSRF token to another domain.
		success: function(result) {
			$("#common-error").removeClass('error success')	
			$("#common-error").addClass('success')	
			$("#common-error").text(result.common)	
			setTimeout(function(){
				window.location.href = '/'
			},3000)
		},
		error: function(jqXHR, textStatus, errorThrown){
			$("#common-error").removeClass('error success')	
			$("#common-error").addClass('error')	
			$("#common-error").text(jqXHR.responseJSON.common)	
		}
	});
}

$(document).ready( function(){
	$("#signup").on('click',OnSignup)
})
				
Теперь разберёмся с ним по порядку.
Что такое csrftoken ? CSRF token (Cross-Site Request Forgery) – это уникальная, генерируемая сервером строка. Она позволяет защититься от подделки хацкерами запросов на сервер, для несанкционированного доступа к ресурсам пользователя сайта.
Чтобы форма смогла быть отправлена методом POST необходимо передать данный токен на сервер череза запрос.
Дальше мы собираем данные с заполненной формы и сохраняем в объект формы который и будем отправлять на сервер.

var form_data = new FormData();
form_data.append("csrfmiddlewaretoken", csrftoken);
form_data.append("username", $("#username").val())
form_data.append("email", $("#email").val())
form_data.append("password",  $("#password").val())
form_data.append("repassword", $("#repassword").val())
				
Определяем метод для отправки, адресс по которому будем обращаться на сервер. Его нужно запомнить, ибо нужно будет создать соответствующую функцию для обработки этого адресса.
Дальше, мы определяем поведение нашего скрипта. При возвращении кода 200, то есть пользователь успешно зарегистрировался, мы добавим надпись над формой и перенаправим его на главную страницу.

success: function(result) {
	$("#common-error").removeClass('error success')	
	$("#common-error").addClass('success')	
	$("#common-error").text(result.common)	
	setTimeout(function(){
	window.location.href = '/'
	},3000)
},
				
При неудаче, то есть при возвращении кода 406 ( в моём случае), мы дадим совсем другое сообщение для пользователя. Чтобы он исправил выявленные ошибки.

error: function(jqXHR, textStatus, errorThrown){
	$("#common-error").removeClass('error success')	
	$("#common-error").addClass('error')	
	$("#common-error").text(jqXHR.responseJSON.common)	
}
				
Ну и в конце, как я уже привык, настраиваю соответствующие события. В данном случае, нажатие кнопки Singup.

Ajax запрос для входа

Всё таже методика, только для подтверждения, то есть, входа на сайт. И здесь будет отправляться чуть меньше данных чем при регистрации.

const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
function OnSignin(){
	var form_data = new FormData();
	form_data.append("csrfmiddlewaretoken", csrftoken);
	form_data.append("username", $("#username").val())
	form_data.append("password",  $("#password").val())
	$.ajax({
		type: "POST",
		url: "/login/verify/",
		data: form_data,
		processData: false,
		contentType: false,
		headers: {'X-CSRFToken': csrftoken},
		mode: 'same-origin',
		success: function(result) {
			$("#common-error").text(result.common)	
			setTimeout(function(){
				$("#common-error").removeClass('error success')	
				$("#common-error").addClass('error')	
				window.location.href = '/'
			},3000)
		},
		error: function(jqXHR, textStatus, errorThrown){
			$("#common-error").removeClass('error success')	
			$("#common-error").addClass('error')	
			$("#common-error").text(jqXHR.responseJSON.common)	
		}
	});
}

$(document).ready( function(){
	$("#signin").on('click',OnSignin)
})
				

Создание и настройка django модели пользователя

Теперь, когда у тебя есть работающие запросы, нужно сделать соответствующую модель для пользователя. В файле models.py добавь:

from django.db import models
from django.template.defaultfilters import slugify
from django.urls import reverse


class Users(models.Model):
	name = models.CharField(max_length=256, unique=True)
	slug = models.SlugField(max_length=256, unique=True)
	email = models.EmailField(blank=False, unique=True)
	password = models.CharField(max_length=256, blank=False)

	def __str__(self):
	return self.name

	def get_absolute_url(self):
	return reverse(type, kwargs={"slug": self.slug})

	def save(self, *args, **kwargs):
	if not self.pk:
	# Newly created object, so set slug
	self.slug = slugify(self.name)

	super(Users, self).save(*args, **kwargs)
				
Не забуть применить миграции для базы данных

./manage.py makemigrations
./manage.py migrate
				

Настройка видов

Теперь у тебя есть модель пользователя, скрипты для отправки запросов на сервер, шаблоны, стили. В конце концов база сайта на django. Что ещё нам нужно? Я оставил самое главное на потом, а именно валидацию, аутентификацию и авторизацию пользователей. Всё это происходит на сервере. Я добавил ещё 3 путя в urls.py в приложении Auth. Итого ваш Auth/urls.py должен выглядеть примерно так:

from django.urls import path
from .views import login, signup, logout, login_verify, signup_verify

urlpatterns = [
	path('login/', login, name='login'),
	path('signup/', signup, name='signup'),
	path('logout/', logout, name='logout'),
	path('login/verify/', login_verify),
	path('signup/verify/', signup_verify),
]
				
Теперь разберём каждый из них по отдельности. Обсудим все нюансы, так сказать.

Вид для регистрации

Если описать в общем данную функцию, то это набор самых разнообразных проверок. Таких как, длина пароля, соответствие формата адреса почты и прочие. И если все проверки пройдены, создаётся пользователь.

def signup_verify(request):
	data = {
		'common': ' | '
	}
	status = 200
	if request.method == 'POST':
		regex = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b'
		username = request.POST['username']
		email = request.POST['email']
		password = request.POST['password']
		repeated_password = request.POST['repassword']
	if len(username) == 0:
		data['common'] += '⚠ user field is empty | '
		status = 406
	if len(email) == 0:
		data['common'] += '⚠ email is empty | '
		status = 406
	if len(password) == 0:
		data['common'] += '⚠ password is empty | '
		status = 406
	# Check if user already exist
	if Users.objects.filter(name=username).exists() or Users.objects.filter(slug=slugify(username)).exists():
		data['common'] += '⚠ user with such name already exist | '
		status = 406
	# Check if email already used
	if Users.objects.filter(email=email).exists():
		data['common'] += '⚠ this email already in use | '
		status = 406
	# Check if username's length is big enough
	if len(username) < 3:
		data['common'] += '⚠ too short user name | '
		status = 406
	# Check if username's length not to big
	if len(username) > 25:
		data['common'] += '⚠ too long user name | '
		status = 406
	# Check if password length is big enough
	if len(password) < 6:
		data['common'] += '⚠ too short password | '
		status = 406
	# Password does not match
	if password != repeated_password:
		data['common'] += '⚠ passwords mismatch | '
		status = 406
	# Email addres does not right
	if not re.fullmatch(regex, email):
		data['common'] += '⚠ email formant are not correct | '
		status = 406
	if status == 200:
		data['common'] = '✔ you have successfully sign up | '
		user = Users(name=username, email=email, password=password)
		user.save()
		return JsonResponse(data, status=status)

	else:
		status = 403
		data['common'] = "Allowed only POST request"
		return JsonResponse(data, status=status)
				

Вид для входа

Функция login это тоже куча проверок на правильность введённых данных. За тем исключением что мы создаём дополнительные переменные в текущей сессии, такие как is_auth и username. И если всё введённые данные верны, функция вернёт соответствующее сообщение и код.

def login_verify(request):
	data = {
	'common': ' | ',
	}
	status = 200
	if request.method == 'POST':
		username = request.POST['username']
		password = request.POST['password']
	# username,email,password fields does not filled up
	if len(username) == 0:
		data['common'] += '⚠ user name field is empty | '
		status = 406
	if len(password) == 0:
		data['common'] += '⚠ password field is empty | '
		status = 406

	# Check if user already exist
	required_user = Users.objects.filter(name=username)
	if required_user.exists():
		# Password does match
		if required_user.first().password == password:
			status = 200
			request.session["is_auth"] = True
			request.session["username"] = username
			data['common'] = '✔ You have successfully sign in, redirecting ... '
		else:
			data['common'] = '⚠ Wrong password'
			status = 406
	else:
		data['common'] = '⚠ No such user in database'
		status = 406

	return JsonResponse(data, status=status)
				

Вид для выхода


def logout(request):
	request.session.flush()
	return HttpResponseRedirect("/")
				
Здесь всё довольно просто. Мы говорим серверу чтобы он очистил(забыл) об текущей сессии с пользователем, а потом перенаправил на главную.

Пример использования аутентификации

Теперь, когда наш сайт может регистрировать новых пользователей и определять вход уже существующий, встаёт вопрос. А что собственно говоря делать дальше ? Во-первых, предлагаю менять надпись login на logout. Для этого модифицируем нашу домашнюю страницу. В home.html Вместо

			
<li>
<a class="nav__link nav__link_act" href="{% url 'login' %}">Login</a>
</li>
			
				
Вставьте

			
{% if not request.session.is_auth %}
<li>
<a class="nav__link nav__link_act" href="{% url 'login' %}">Login</a>
</li>
{% else%}
<li>
<a class="nav__link nav__link_act" href="{% url 'logout' %}">Logout</a>
</li>
{% endif %}
			
				
Во-вторых предлагаю отобразить имя и почту нашего пользователя. Вставьте этот кусочек шаблона после последнего элемента списка

			
{% if request.session.is_auth %}
<li>
<div class="nav__link" >{{ user.name }}</div>
<div class="nav__link" >{{ user.email }}</div>
</li>
{% endif %}
			
				
А в Main/views.py нужно будет улучшить функцию home

from django.shortcuts import render
from Auth.models import Users


def home(request):
	user = Users.objects.filter(name=request.session.get('username', 'Guest')).first()
	context = {
	'user': user,
	}
	return render(request, 'Main/home.html', context=context)

				
Сначала мы импортировали модель Users. Потом находим пользователя по сохранённому нами ранее значению в сессии, username. Мы его получили при логировании, если ты ещё не забыл.
Перезагрузив страницу и залогинившись, вы будете перенаправлены на главную и увидите что-то вроде этого тестовый сайт где отображается интерфейс авторизации
Вот такое вот базовое применение системы аутентификации. Ещё можно много чего сделать с этим. Например, создать отдельную страницу для каждого пользователя или предоставить возможность коментирования. Но это уже выходит за рамки данной статьи. Моя цель была показать тебе, как можно реализовать систему аутентификации пользователей самостоятельно на django, а уже как её использовать тебе решать.

Выводы

Теперь, у вас есть всё необходимое для создания собственных форм регистрации и авторизации пользователей. Это было не сложно, правда ? Хотя, с не привычки, может показаться иначе. Подытоживая.
Надеюсь, затраченое время было затрачено не зря. И вы что-нибудь да и извлекли для себя.

Для ищущих готовое решение

Вот ссылка на скачивание готового django приложения.
Или ссылка на скачивание всего django проекта, уже готового.
Или если не хотите бездумно скачивать файлы с неизвестных сайтов, вот ссылка на github репозиторий.
Если вы решили скачать приложение, поздравляю вы улавливаете основную особенность django, а именно его модульность. Вот так можно будет его подключить к вашему сайту.В settings.py добавить приложение

INSTALLED_APPS = [ 
'Auth.apps.AuthConfig'
]
				
В urls.py на уровне проекта, подключить маршруты.

urlpatterns += (

path('', include('Auth.urls')),

)
				
Всё готово, и настроенно.

Использованые материалы и полезные ресурсы


сердце 2
3 соединённые точки 0