SaaS «Планёрка» выходит из беты: нужны аккаунты. Регистрация с хешем пароля, вход с токеном, middleware-охранник и разница между 401, 403 и 404.
Ты делаешь таск-трекер «Планёрка». В бете было просто: один общий список, все видят все задачи. Для трёх друзей-тестеров — нормально. Но сегодня продукт открывается для всех, и первый же пользователь спросит: «Почему я вижу чужие задачи?!»
В этом проекте ты построишь полный цикл работы с аккаунтами: регистрация → вход → токен → защищённые маршруты → выход.
Два слова, которые постоянно путают:
| Вопрос | Пример в «Планёрке» | Код ошибки | |
|---|---|---|---|
| Аутентификация | Кто ты? | вход по email и паролю | 401 |
| Авторизация | Что тебе можно? | удалять можно только свои задачи | 403 |
Сначала сервер выясняет, кто пришёл. Потом решает, что этому кому-то разрешено.
1. POST /api/auth/register → аккаунт создан
2. POST /api/auth/login → сервер выдал токен: { token: "tok_k3j9x2mp" }
3. Клиент сохраняет токен и шлёт его с каждым запросом:
GET /api/me
Authorization: Bearer tok_k3j9x2mp
4. middleware auth проверяет токен → кладёт пользователя в req.user
5. Обработчик отвечает данными ИМЕННО этого пользователяТокен — это пропуск: случайная строка, которую сервер выдал после проверки пароля и запомнил у себя: sessions[token] = userId. Пароль клиент отправляет один раз — при входе. Дальше по сети ходит только токен.
| Код | Смысл | Когда |
|---|---|---|
401 Unauthorized | «Я не знаю, кто ты» | нет токена или он неверный |
403 Forbidden | «Я знаю, кто ты — и тебе нельзя» | попытка удалить чужую задачу |
404 Not Found | «Такого не существует» | задачи с таким id нет |
Эту таблицу разберём подробно на шаге про удаление — она из тех, что спрашивают на собеседованиях.
1. Про hash(). В песочнице мы используем крошечную учебную функцию hash() — она показывает главный принцип: хранить не пароль, а необратимое преобразование пароля. Для реальной защиты она не годится. В настоящем проекте — только bcrypt, и на последнем шаге мы его подключим. Свои функции хеширования в реальном коде не пишут никогда.
2. Про заголовки. Клиент отправляет заголовок Authorization: Bearer ..., но Express приводит все имена заголовков к нижнему регистру. Читать нужно req.headers.authorization — с маленькой буквы. Забыть про это — классическая ошибка первого auth-проекта.
Ниже — стартовый скелет: данные в памяти и план того, что появится. Один пользователь уже есть — Иван с паролем secret123 и двумя задачами.
const app = express();
app.use(express.json());
// УЧЕБНАЯ заглушка вместо bcrypt: принцип — хранить не пароль, а необратимое преобразование.
// В реальном проекте — только bcrypt. Свою функцию хеширования никогда не пиши.
function hash(p) { var h = 0; for (var i = 0; i < p.length; i++) h = (h * 31 + p.charCodeAt(i)) | 0; return 'h' + Math.abs(h); }
function makeToken() { return 'tok_' + Math.random().toString(36).slice(2, 10); }
let users = [{ id: 1, email: 'ivan@example.com', passwordHash: hash('secret123') }];
let nextUserId = 2;
let sessions = {}; // token -> userId
let tasks = [
{ id: 1, userId: 1, title: 'Подготовить релиз' },
{ id: 2, userId: 1, title: 'Ревью PR' },
];
let nextTaskId = 3;
// Дальше здесь появятся:
// POST /api/auth/register — регистрация
// POST /api/auth/login — вход, выдаёт токен
// function auth(...) — middleware-охранник
// GET /api/me — профиль (только с токеном)
// GET/POST /api/tasks — задачи текущего пользователя
// DELETE /api/tasks/:id — с проверкой владельца (403)
// POST /api/auth/logout — выход
app.listen(3000, () => console.log('✅ http://localhost:3000'));Клиент шлёт { email, password }. Проверки от простого к сложному: поля есть, пароль ≥ 6 символов, email свободен. Пароль не хранится — в базу уходит только его необратимый хеш.