Обработка аутентификации в Express.js
В этой статье мы собираемся сделать простое приложение, чтобы продемонстрировать, как вы можете обрабатывать аутентификацию в Express.js. Поскольку мы будем использовать некоторые базовые синтаксисы ES6 и платформу Bootstrap для разработки пользовательского интерфейса, это может помочь, если у вас есть базовые знания об этих технологиях.
Несмотря на то, что вам может потребоваться использовать базу данных в реальных приложениях, поскольку нам нужно сделать эту статью простой, мы не будем использовать какие-либо базы данных или методы проверки электронной почты, такие как отправка электронной почты с кодом проверки.
Настройка проекта
Во-первых, давайте создадим новую папку с именем, скажем, simple-web-app
. Используя терминал, мы перейдем к этой папке и создадим каркасный проект Node.js:
npm init
Теперь мы также можем установить Express:
npm install --save express
Для простоты мы будем использовать серверный движок рендеринга под названием Handlebars. Этот движок будет отображать наши HTML-страницы на стороне сервера, поэтому нам не понадобятся никакие другие передовые фреймворки, такие как Angular или React.
Давайте продолжим и установим express-handlebars
:
npm install --save express-handlebars
Мы также будем использовать два других промежуточных пакета Express (body-parser
и cookie-parser
) для анализа тела HTTP-запросов и анализа необходимых файлов cookie для аутентификации:
npm install --save body-parser cookie-parser
Реализация
Приложение, которое мы собираемся создать, будет содержать «защищенную» страницу, которую могут посещать только зарегистрированные пользователи, в противном случае они будут перенаправлены на домашнюю страницу, предлагая либо войти, либо зарегистрироваться.
Для начала давайте импортируем библиотеки, которые мы ранее установили:
const express = require('express');
const exphbs = require('express-handlebars');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
Мы будем использовать собственный модуль crypto
для хеширования паролей и генерации токена аутентификации - это будет подробно описано чуть позже в этой статье.
Далее, давайте создадим простое приложение Express и сконфигурируем промежуточное ПО, которое мы импортировали, вместе с движком Handlebars:
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.engine('hbs', exphbs({
extname: '.hbs'
}));
app.set('view engine', 'hbs');
app.listen(3000);
По умолчанию в Handlebars расширение шаблона должно быть .handlebars
. Как вы можете видеть в этом коде, мы настроили наш шаблонизатор для поддержки файлов с более коротким расширением .hbs
. Теперь давайте создадим несколько файлов шаблонов:
Папка layouts
внутри папки view
будет держать ваш основной макет, который обеспечит базовый HTML для других шаблонов.
Давайте создадим нашу главную страницу main.hbs
:
Document
{{{body}}}
Другие шаблоны будут отображаться внутри тега {{{body}}}
. У нас есть шаблон HTML и необходимые файлы CSS и JS для Bootstrap, импортированные в этот макет.
Закончив нашу основную оболочку, давайте создадим страницу home.hbs
, где пользователям будет предложено войти в систему или зарегистрироваться:
Затем давайте создадим обработчик запроса к пути root path (/
) для визуализации шаблона home.
app.get('/', function (req, res) {
res.render('home');
});
Давайте запустим наше приложение и перейдем к http://localhost:3000
:
Регистрация аккаунта
Информация об учетной записи собирается через страницу registration.hbs
:
{{#if message}}
{{/if}}
В этом шаблоне мы создали форму с полями регистрации пользователя, которая представляет собой имя, фамилию, адрес электронной почты, пароль и подтверждение пароля, и задали наше действие в качестве маршрута /register
. Кроме того, у нас есть поле сообщения, в котором мы будем отображать сообщения об ошибках и успехах для примера, если пароли не совпадают, и т.д.
Давайте создадим дескриптор запроса для отображения шаблона регистрации при посещении пользователя http://localhost:3000/register
:
app.get('/register', (req, res) => {
res.render('register');
});
Из - за соображений безопасности, хорошая практика создавать хэш пароля с сильным алгоритмом хэширования, как SHA256
. Хешируя пароли, мы гарантируем, что даже если наша база паролей может быть скомпрометирована, пароли не будут видны в текстовом формате.
Еще лучший метод, чем простое хеширование, - использовать соль, как в алгоритме bcrypt.
const crypto = require('crypto');
const getHashedPassword = (password) => {
const sha256 = crypto.createHash('sha256');
const hash = sha256.update(password).digest('base64');
return hash;
}
Когда пользователь отправляет регистрационную форму, POST запрос будет отправлен на путь /register
.
При этом нам нужно обработать этот запрос с помощью информации из формы и сохранить нашего вновь созданного пользователя. Как правило, это делается путем сохранения пользователя в базе данных, но для простоты мы будем хранить пользователей в массиве JavaScript.
Так как каждый перезапуск сервера будет повторно инициализировать массив, мы будем жестко кодировать пользователя для целей тестирования, которые будут инициализироваться каждый раз:
const users = [
// Этот пользователь добавляется в массив, чтобы избежать создания нового пользователя при каждом перезапуске
{
firstName: 'John',
lastName: 'Doe',
email: 'johndoe@email.com',
// Это хэш SHA256 для значения `password`
password: 'XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg='
}
];
app.post('/register', (req, res) => {
const { email, firstName, lastName, password, confirmPassword } = req.body;
// Проверьте, совпадают ли пароль
if (password === confirmPassword) {
// Проверьте, зарегистрирован ли пользователь с тем же адресом электронной почты
if (users.find(user => user.email === email)) {
res.render('register', {
message: 'User already registered.',
messageClass: 'alert-danger'
});
return;
}
const hashedPassword = getHashedPassword(password);
// Сохранить пользователя в базе данных, если вы его используете
users.push({
firstName,
lastName,
email,
password: hashedPassword
});
res.render('login', {
message: 'Registration Complete. Please login to continue.',
messageClass: 'alert-success'
});
} else {
res.render('register', {
message: 'Password does not match.',
messageClass: 'alert-danger'
});
}
});
Принимаемые email
, firstName
, lastName
,password
, и confirmPassword
проверяются - пароли совпадают, адрес электронной почты не зарегистрирован и т.д.
Если каждая проверка прошла успешно, мы хэшируем пароль и храним информацию в массиве и перенаправляем пользователя на страницу входа. В противном случае мы будем повторно отображать страницу регистрации с сообщением об ошибке.
Теперь давайте посетим страницу /register
, чтобы проверить, что она работает правильно:
Логин аккаунта
Теперь мы можем реализовать функционал входа в систему. Давайте начнем с создания страницы login.hbs
:
{{#if message}}
{{/if}}
А затем давайте создадим обработчик для этого запроса:
app.get('/login', (req, res) => {
res.render('login');
});
Эта форма будет отправлять POST запрос на /login
, когда пользователь отправляет форму. Тем не менее, еще одна вещь, которую мы будем делать, это отправка токена аутентификации для входа в систему. Этот токен будет использоваться для идентификации пользователя, и каждый раз, когда он отправляет HTTP-запрос, этот токен будет отправляться с помощью cookie:
const generateAuthToken = () => {
return crypto.randomBytes(30).toString('hex');
}
С помощью нашего вспомогательного метода мы можем создать обработчик запросов для страницы входа в систему:
// Тут будут хранится пользователи и authToken, связанных с ними
const authTokens = {};
app.post('/login', (req, res) => {
const { email, password } = req.body;
const hashedPassword = getHashedPassword(password);
const user = users.find(u => {
return u.email === email && hashedPassword === u.password
});
if (user) {
const authToken = generateAuthToken();
// Сохранить токен аутентификации
authTokens[authToken] = user;
// Установка токена авторизации в куки
res.cookie('AuthToken', authToken);
// Перенаправить пользователя на защищенную страницу
res.redirect('/protected');
} else {
res.render('login', {
message: 'Invalid username or password',
messageClass: 'alert-danger'
});
}
});
В этом обработчике запросов authTokens
используется для хранения токенов аутентификации в качестве ключа и соответствующего пользователя в качестве значения, что позволяет использовать простой токен для поиска пользователя. Вы можете использовать базу данных, такую как Redis, или любую другую базу данных для хранения этих токенов - мы используем этот объект для простоты.
Давайте перейдем на /login
:
Мы еще не совсем закончили. Нам нужно авторизовать пользователя, прочитав из cookie authToken
после получения запроса. Над всеми обработчиками запросов и под cookie-parser
промежуточным программным обеспечением давайте создадим наше собственное промежуточное программное обеспечение для внедрения пользователей в запросы:
app.use((req, res, next) => {
// Получение значения из cookie
const authToken = req.cookies['AuthToken'];
// Добавление авторизованного пользователя в запрос
req.user = authTokens[authToken];
next();
});
Теперь мы можем использовать req.user
внутри наших обработчиков запросов, чтобы проверить, аутентифицирован ли пользователь с помощью токена.
Наконец, давайте создадим обработчик запросов для отображения защищенной страницы protected.hbs
:
This page is only visible to logged in users
И обработчик запроса для страницы:
app.get('/protected', (req, res) => {
if (req.user) {
res.render('protected');
} else {
res.render('login', {
message: 'Please login to continue',
messageClass: 'alert-danger'
});
}
});
Как видите, вы можете использовать, req.user
чтобы проверить, аутентифицирован ли пользователь. Если этот объект пуст, пользователь не аутентифицирован.
Другой способ требовать аутентификации на маршрутах - это реализовать его в качестве промежуточного программного обеспечения, которое затем можно применить к маршрутам напрямую, как они определены в объекте app
:
const requireAuth = (req, res, next) => {
if (req.user) {
next();
} else {
res.render('login', {
message: 'Please login to continue',
messageClass: 'alert-danger'
});
}
};
app.get('/protected', requireAuth, (req, res) => {
res.render('protected');
});
Стратегии авторизации также могут быть реализованы таким образом, назначая роли пользователям, а затем проверяя правильные разрешения, прежде чем пользователь получит доступ к странице.
Вывод
Аутентификация пользователя в Express довольно проста и понятна. Мы использовали модуль crypto
для хэширования паролей зарегистрированных пользователей в качестве базовой функции безопасности и создали защищенную страницу, видимую только пользователям, аутентифицированным с помощью токена.
Исходный код этого проекта можно найти на GitHub
Contribute to jkasun/stack-abuse-express-authentication development by creating an account on GitHub.