Авторизация и безопасность
Сайт перестаёт быть «поделкой» в момент, когда в нём появляются пользователи и их данные. Научимся хранить пароли, выдавать токены и защищаться от самых частых атак.
Пароли: храним только хэш
Пароль в открытом виде — катастрофа. Храним не его, а хэш с «солью» — случайной строкой на каждого пользователя. Надёжный пакет — bcrypt.
npm install bcrypt
const bcrypt = require("bcrypt");
const hash = await bcrypt.hash(plainPassword, 10); // соль и хэш
const ok = await bcrypt.compare(plainPassword, hash); // проверка
Сессии и JWT
После входа сервер должен «помнить» пользователя. Два основных способа: сессии (ид в cookie + хранилище на сервере) и JWT — криптографическая подпись в самом токене.
const jwt = require("jsonwebtoken");
// выдача токена при входе
const token = jwt.sign({ id: user.id }, SECRET, { expiresIn: "7d" });
// клиент кладёт его в localStorage/cookie и шлёт с каждым запросом
// проверка на защищённых маршрутах
function auth(req, res, next) {
const token = req.headers.authorization?.split(" ")[1];
try { req.user = jwt.verify(token, SECRET); next(); }
catch { res.status(401).json({ error: "Нет доступа" }); }
}
app.get("/profile", auth, (req, res) => res.json({ user: req.user }));
XSS: чужие скрипты на вашей странице
Если вставлять в HTML данные пользователя как есть, можно «запустить» чужой код в чужом браузере. Пример: комментарий со злым <script>. Лечится экранированием вывода.
// опасно:
element.innerHTML = userComment;
// безопасно: textContent сам экранирует <, >, "
element.textContent = userComment;
// на сервере — библиотека escape-html
const escapeHtml = require("escape-html");
res.send("<p>" + escapeHtml(userComment) + "</p>");
CSP и основы
Content-Security-Policy — заголовок, который запрещает браузеру исполнять что-то, чего вы не разрешили (инлайн-скрипты, чужие домены). Плюс HTTPS по умолчанию (урок 20), helmet для набора безопасных заголовков.
npm install helmet
app.use(helmet()); // безопасные заголовки одной строкой
process.env), а не в коде.