Encodera

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

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

Оглавление

Авторизация через 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 — и никогда открытым текстом.

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

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