Cookie и простая сессия
Cookie — небольшая запись, которую сервер просит браузер сохранить. Браузер сам отправляет её обратно с каждым запросом к тому же сайту. Для HTML-страниц это удобнее JWT в заголовке: после входа браузер помнит пользователя без лишнего кода на странице. В Hono функции для cookie лежат в hono/cookie.
Поставить и прочитать 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 задаётся в секундах.
Параметры cookie
| Параметр | Что делает |
|---|---|
path: '/' | cookie отправляется для всех адресов сайта |
maxAge | срок жизни в секундах |
httpOnly: true | JavaScript на странице не может прочитать cookie |
secure: true | cookie передаётся только по HTTPS |
sameSite: 'Lax' | не отправляется при запросах с чужих сайтов, кроме переходов по ссылке |
Подписанная cookie как сессия
Обычную 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.