Вход и выход пользователей
Аутентификация — проверка, кто перед нами: посетитель вводит почту и пароль, сервер сверяет их и запоминает пользователя в сессии. Пароли нельзя хранить в открытом виде — в базу записывают хеш: строку, из которой исходный пароль получить нельзя, но с которой можно сверить введённый.
Таблица и модель
npm install bcryptВ src/db.js добавьте таблицу пользователей:
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL
);И создайте модель src/models/user.js:
// src/models/user.js
import bcrypt from 'bcrypt';
import db from '../db.js';
export async function create(email, password) {
const hash = await bcrypt.hash(password, 12);
const result = db.prepare('INSERT INTO users (email, password_hash) VALUES (?, ?)').run(email, hash);
return Number(result.lastInsertRowid);
}
export async function verify(email, password) {
const user = db.prepare('SELECT * FROM users WHERE email = ?').get(email);
if (!user || !(await bcrypt.compare(password, user.password_hash))) {
return null;
}
return user;
}bcrypt.hash() превращает пароль в хеш, число 12 задаёт, сколько труда это стоит: подбирать пароли перебором становится очень медленно. bcrypt.compare() сверяет пароль с хешем.
Регистрация, вход, выход
Контроллер src/controllers/authController.js записывает в сессию только номер пользователя:
// src/controllers/authController.js
import * as User from '../models/user.js';
export async function register(req, res) {
const id = await User.create(req.body.email, req.body.password);
req.session.userId = id;
res.redirect('/posts');
}
export async function login(req, res, next) {
const user = await User.verify(req.body.email, req.body.password);
if (!user) {
return res.status(401).render('auth/login', { title: 'Вход', error: 'Неверная почта или пароль' });
}
req.session.regenerate((error) => {
if (error) return next(error);
req.session.userId = user.id;
res.redirect('/posts');
});
}
export function logout(req, res) {
req.session.destroy(() => res.redirect('/'));
}regenerate() выдаёт новую сессию после входа: так злоумышленник не сможет заранее подсунуть посетителю свой номер сессии. destroy() при выходе удаляет сессию целиком.
Маршруты подключаются как обычно: router.post('/register', ...), router.post('/login', ...), router.post('/logout', ...). Выход делают POST-формой с одной кнопкой, а не ссылкой.
Защита маршрутов
Создайте middleware src/middleware/requireAuth.js:
// src/middleware/requireAuth.js
export function requireAuth(req, res, next) {
if (!req.session.userId) {
return res.redirect('/login');
}
next();
}И поставьте её перед обработчиками, которые доступны только после входа:
// src/routes/posts.js
import { requireAuth } from '../middleware/requireAuth.js';
router.get('/new', requireAuth, postsController.create);
router.post('/', requireAuth, postsController.store);Откройте /posts/new, не войдя на сайт, — браузер перейдёт на /login. После входа форма откроется.
Частая ошибка
Не сообщайте, что именно не так: «такой почты нет» или «неверный пароль». Так любой может проверить, зарегистрирован ли человек на сайте. Отвечайте одним общим сообщением «Неверная почта или пароль», как в login() выше.