Авторизация через JWT
Писать и удалять статьи должен только автор блога. API не хранит сессий, поэтому после входа сервер выдаёт клиенту токен — JWT (JSON Web Token), подписанную строку с данными пользователя. Клиент присылает токен в каждом запросе, а сервер проверяет подпись.
Установка и настройки
npm install @fastify/jwtЛогин и пароль автора добавьте в .env и в схему @fastify/env из урока о настройках:
ADMIN_NAME=anna
ADMIN_PASSWORD=очень-длинный-пароль
JWT_SECRET=длинная-случайная-строка-для-подписиПлагин авторизации
Создайте src/plugins/auth.js:
// src/plugins/auth.js
import fp from 'fastify-plugin';
import fastifyJwt from '@fastify/jwt';
async function auth(app) {
app.register(fastifyJwt, {
secret: app.config.JWT_SECRET,
sign: { expiresIn: '1h' },
});
app.decorate('authenticate', async (request) => {
await request.jwtVerify();
});
}
export default fp(auth);@fastify/jwt добавляет app.jwt.sign() для выдачи токена и request.jwtVerify() для проверки. Если токена нет или он неверный, jwtVerify() бросает ошибку с кодом 401. Подключите плагин в src/app.js после @fastify/env.
Вход
Создайте src/routes/auth.js и подключите его в src/app.js:
// src/routes/auth.js
export default async function authRoutes(app) {
app.post('/login', async (request) => {
const { name, password } = request.body;
if (name !== app.config.ADMIN_NAME || password !== app.config.ADMIN_PASSWORD) {
throw app.httpErrors.unauthorized('Неверный логин или пароль');
}
const token = app.jwt.sign({ name });
return { token };
});
}curl -X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{"name":"anna","password":"очень-длинный-пароль"}'Сервер вернёт {"token":"eyJhbGciOi..."} — токен, действующий один час.
Защита маршрутов
Добавьте app.authenticate в хук onRequest маршрутов, которые меняют данные:
// src/routes/posts.js
app.post('/posts', {
onRequest: [app.authenticate],
schema: { body: postInput },
}, async (request, reply) => {
request.log.info({ author: request.user.name }, 'Новая статья');
// ...создание статьи, как в уроке о CRUD
});После успешной проверки данные из токена лежат в request.user. Запрос без токена получит 401, а с токеном пройдёт:
curl -X POST http://localhost:3000/posts \
-H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" \
-d '{"title":"Защищённая статья","body":"Текст"}'Так же добавьте onRequest к маршрутам PUT и DELETE. Чтение статей остаётся открытым.
Частая ошибка
Пароль в .env годится только для учебного блога с одним автором. Когда пользователей много, их пароли хранят в базе только в виде хеша — например, через пакеты bcrypt или argon2 — и никогда открытым текстом.