Encodera

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

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

Оглавление

Cookie и простая сессия

Cookie — небольшая запись, которую сервер просит браузер сохранить. Браузер сам отправляет её обратно с каждым запросом к тому же сайту. Для HTML-страниц это удобнее JWT в заголовке: после входа браузер помнит пользователя без лишнего кода на странице. В Hono функции для cookie лежат в hono/cookie.

// src/routes/pages.tsx
import { getCookie, setCookie } from 'hono/cookie'
 
pages.get('/theme/:name', (c) => {
  setCookie(c, 'theme', c.req.param('name'), { path: '/', maxAge: 60 * 60 * 24 * 30 })
  return c.redirect('/')
})
 
pages.get('/theme', (c) => {
  const theme = getCookie(c, 'theme') ?? 'светлая'
  return c.text(`Тема: ${theme}`)
})

Откройте /theme/dark, затем /theme — появится «Тема: dark». Cookie проживёт 30 дней: maxAge задаётся в секундах.

ПараметрЧто делает
path: '/'cookie отправляется для всех адресов сайта
maxAgeсрок жизни в секундах
httpOnly: trueJavaScript на странице не может прочитать cookie
secure: truecookie передаётся только по HTTPS
sameSite: 'Lax'не отправляется при запросах с чужих сайтов, кроме переходов по ссылке

Обычную cookie пользователь может изменить в браузере. Подписанная cookie хранит рядом с значением подпись секретным ключом: подделку сервер не примет. Этого достаточно для простого входа:

// src/routes/pages.tsx
import { deleteCookie, getSignedCookie, setSignedCookie } from 'hono/cookie'
import { jwtSecret } from '../auth.js'
 
pages.get('/login', (c) => {
  return c.render(
    <form method="post" action="/login">
      <input name="username" placeholder="Логин" />
      <input name="password" type="password" placeholder="Пароль" />
      <button>Войти</button>
    </form>,
    { title: 'Вход' },
  )
})
 
pages.post('/login', async (c) => {
  const body = await c.req.parseBody()
  if (body.username !== 'anna' || body.password !== process.env.ADMIN_PASSWORD) {
    return c.redirect('/login')
  }
  await setSignedCookie(c, 'user', 'anna', jwtSecret, {
    path: '/',
    httpOnly: true,
    sameSite: 'Lax',
    maxAge: 60 * 60 * 8,
  })
  return c.redirect('/admin')
})
 
pages.get('/admin', async (c) => {
  const user = await getSignedCookie(c, jwtSecret, 'user')
  if (!user) {
    return c.redirect('/login')
  }
  return c.render(<h1>Здравствуйте, {user}</h1>, { title: 'Панель' })
})
 
pages.post('/logout', (c) => {
  deleteCookie(c, 'user', { path: '/' })
  return c.redirect('/')
})

Форма /login отправляет логин и пароль, сервер ставит подписанную cookie user и перенаправляет на /admin. Там getSignedCookie() возвращает значение или false, если cookie нет или подпись не совпала. Для простоты здесь использован тот же секрет, что и для JWT; в реальном проекте лучше завести отдельный.

Сессии на сервере

Hono не хранит сессии сам. Если нужно хранить на сервере больше данных — корзину, настройки, — заводят таблицу sessions, кладут в cookie только случайный идентификатор и по нему находят запись в базе.

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

Cookie входа ставят без httpOnly: true. Тогда любой скрипт на странице, в том числе внедрённый злоумышленником, может её прочитать и отправить на чужой сервер. Cookie, которые нужны только серверу, всегда помечайте httpOnly, а на сайте с HTTPS добавляйте secure: true.

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

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