Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Вход и выход пользователей

Аутентификация — проверка, кто перед нами: посетитель вводит почту и пароль, сервер сверяет их и запоминает пользователя в сессии. Пароли нельзя хранить в открытом виде — в базу записывают хеш: строку, из которой исходный пароль получить нельзя, но с которой можно сверить введённый.

Таблица и модель

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() выше.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.