Encodera

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

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

Оглавление

Вход по JWT

Сейчас создать или удалить статью может кто угодно. Для API обычно используют JWT — подписанный токен, строку, в которой сервер записывает, кто пользователь, и ставит подпись секретным ключом. Клиент получает токен при входе и присылает его в каждом запросе, а сервер проверяет подпись и не хранит никаких сессий.

Модуль авторизации

Секрет берётся из переменной JWT_SECRET в файле .env. Создайте src/auth.ts:

// src/auth.ts
import { jwt } from 'hono/jwt'
 
const secret = process.env.JWT_SECRET
if (!secret) {
  throw new Error('Не задана переменная JWT_SECRET')
}
 
export const jwtSecret = secret
export const requireAuth = jwt({ secret, alg: 'HS256' })

Если переменной нет, сервер не запустится и сообщит об этом. requireAuth — middleware, которое пропускает запрос только с правильным токеном. HS256 — алгоритм подписи общим секретом.

Выдача токена

Создайте src/routes/auth.ts с маршрутом входа. Для простоты пароль администратора тоже лежит в .env как ADMIN_PASSWORD, в настоящем проекте пользователи хранятся в базе с хешами паролей:

// src/routes/auth.ts
import { Hono } from 'hono'
import { sign } from 'hono/jwt'
import { jwtSecret } from '../auth.js'
 
const auth = new Hono()
 
auth.post('/login', async (c) => {
  const { username, password } = await c.req.json<{ username: string; password: string }>()
 
  if (username !== 'anna' || password !== process.env.ADMIN_PASSWORD) {
    return c.json({ error: 'Неверный логин или пароль' }, 401)
  }
 
  const token = await sign(
    { sub: username, exp: Math.floor(Date.now() / 1000) + 60 * 60 },
    jwtSecret,
  )
  return c.json({ token })
})
 
export default auth

Подключите группу в src/index.ts: app.route('/auth', auth). Поле exp — время окончания действия токена в секундах: через час токен перестанет приниматься.

Защита маршрутов

Добавьте requireAuth в маршруты, которые меняют данные, в src/routes/posts.ts:

// src/routes/posts.ts
import { requireAuth } from '../auth.js'
 
posts.post('/', requireAuth, zValidator('json', postSchema), (c) => {
  const user = c.get('jwtPayload')
  console.log(`Статью создаёт ${user.sub}`)
  // ...создание статьи, как в прошлом уроке
  return c.json({ author: user.sub }, 201)
})

То же сделайте для put и delete. c.get('jwtPayload') возвращает данные из токена. Чтение списка остаётся открытым.

Проверка

curl -X POST http://localhost:3000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "anna", "password": "пароль-из-env"}'

В ответе придёт {"token":"eyJ..."}. Запрос без токена получит 401, а с токеном пройдёт:

curl -X POST http://localhost:3000/posts \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{"title": "Защищённая статья", "body": "Текст"}'

Вместо eyJ... подставьте полученный токен целиком.

Частая ошибка

В токен кладут секретные данные, например пароль или адрес. Подпись защищает токен от подделки, но не прячет содержимое: любую его часть можно прочитать, раскодировав base64. Храните в токене только идентификатор пользователя, роль и срок действия.

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

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