Пользователь выбирает язык интерфейса — русский. Через неделю он снова открывает сайт: язык должен сохраниться. Простое решение — куки: небольшие данные, которые браузер хранит и автоматически отправляет на сервер с каждым HTTP-запросом. В отличие от localStorage — сервер их тоже видит.
Куки решают две задачи: сессия (сервер знает, кто авторизован) и настройки (язык, тема, регион). Главное отличие от localStorage — куки уходят на сервер с каждым запросом.
document.cookie ведёт себя необычно: при чтении возвращает все куки, при записи — добавляет одну:
// Чтение — возвращает ВСЕ куки через "; "
console.log(document.cookie)
// "session_id=abc123; lang=ru; theme=dark"
// Запись — добавляет ОДНУ куку (не перезаписывает остальные!)
document.cookie = "username=ivan_petrov"
document.cookie = "lang=ru"
// Теперь обе куки существуют// expires — дата истечения (UTC строка)
document.cookie = "session=abc; expires=Fri, 31 Dec 2025 23:59:59 GMT"
// max-age — время жизни в секундах (предпочтительнее expires)
document.cookie = "token=xyz; max-age=86400" // 1 день
document.cookie = "prefs=dark; max-age=2592000" // 30 дней
// path — куки видна только по этому пути и ниже
document.cookie = "admin=true; path=/admin"
// domain — для какого домена (с поддоменами: .example.com)
document.cookie = "uid=123; domain=.myshop.ru"
// secure — отправлять только по HTTPS
document.cookie = "token=abc; secure"
// samesite — защита от CSRF
document.cookie = "csrf=xyz; samesite=strict"
// strict — только запросы с того же сайта
// lax — разрешает GET при переходе по ссылке (по умолчанию)
// none — всегда (требует secure)Куки с флагом HttpOnly нельзя прочитать через document.cookie. Они устанавливаются сервером и существуют только в HTTP-запросах:
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=StrictЭто защищает от XSS: даже если злоумышленник выполнит JS, он не сможет украсть такую куку.
function setCookie(name, value, options = {}) {
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`
if (options.maxAge) cookie += `; max-age=${options.maxAge}`
if (options.expires) cookie += `; expires=${options.expires.toUTCString()}`
if (options.path) cookie += `; path=${options.path}`
if (options.domain) cookie += `; domain=${options.domain}`
if (options.secure) cookie += '; secure'
if (options.sameSite) cookie += `; samesite=${options.sameSite}`
if (options.httpOnly) cookie += '; httponly'
document.cookie = cookie
}
function getCookie(name) {
const cookies = parseCookies(document.cookie)
return cookies[name] ?? null
}
function deleteCookie(name, path = '/') {
// Устанавливаем прошедшую дату — браузер удаляет куку
document.cookie = `${encodeURIComponent(name)}=; max-age=0; path=${path}`
}
function parseCookies(cookieString) {
if (!cookieString) return {}
return cookieString
.split('; ')
.reduce((acc, pair) => {
const idx = pair.indexOf('=')
const key = decodeURIComponent(pair.slice(0, idx).trim())
const val = decodeURIComponent(pair.slice(idx + 1))
acc[key] = val
return acc
}, {})
}// Сохранить язык интерфейса на 1 год
setCookie('lang', 'ru', {
maxAge: 365 * 24 * 60 * 60,
path: '/',
sameSite: 'lax',
})
// Прочитать при загрузке страницы
const lang = getCookie('lang') || 'ru'
applyLanguage(lang)
// Удалить при выходе из аккаунта
function logout() {
deleteCookie('session_id')
deleteCookie('csrf_token')
window.location.href = '/login'
}Ошибка 1: хранят чувствительные данные в куке без HttpOnly
// Сломано: доступно через XSS-атаку
document.cookie = 'session_token=abc123'
// Исправлено: сессию устанавливает сервер с HttpOnly
// Set-Cookie: session_token=abc123; HttpOnly; Secure; SameSite=Strict
// HttpOnly — JS вообще не может прочитать эту кукуОшибка 2: не указывают path — кука видна не на всех страницах
// Сломано: кука доступна только на /profile и ниже
document.cookie = 'lang=ru' // path по умолчанию — текущий путь
// Исправлено: path=/ — кука видна везде
document.cookie = 'lang=ru; path=/'Ошибка 3: парсят куки через split('=') — ломается при значениях с '='
// Сломано: JWT-токен содержит '=' в base64
// 'token=eyJ...abc=' → split('=') даст ['token', 'eyJ...abc', '']
// Исправлено: используем indexOf('=') для нахождения первого '='
const idx = pair.indexOf('=')
const key = pair.slice(0, idx).trim()
const val = decodeURIComponent(pair.slice(idx + 1)) // всё после первого '='| | Куки | localStorage |
|---|---|---|
| Отправляются на сервер | Да, автоматически | Нет |
| Размер | ~4 KB | 5-10 MB |
| Срок жизни | Настраивается | До явного удаления |
| HttpOnly | Да (недоступны из JS) | Нет |
| Доступность | Браузер + сервер | Только браузер |
Реализация вспомогательных функций getCookie, setCookie, deleteCookie
// Вспомогательные функции для работы с куками
function parseCookies(cookieString) {
if (!cookieString.trim()) return {}
return cookieString
.split('; ')
.reduce((acc, pair) => {
const idx = pair.indexOf('=')
if (idx === -1) return acc
const key = decodeURIComponent(pair.slice(0, idx).trim())
const val = decodeURIComponent(pair.slice(idx + 1))
acc[key] = val
return acc
}, {})
}
function setCookie(name, value, options = {}) {
let cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value)
if (options.maxAge !== undefined) cookie += '; max-age=' + options.maxAge
if (options.path) cookie += '; path=' + options.path
if (options.domain) cookie += '; domain=' + options.domain
if (options.secure) cookie += '; secure'
if (options.sameSite) cookie += '; samesite=' + options.sameSite
// В браузере: document.cookie = cookie
return cookie // возвращаем для демонстрации
}
function getCookie(cookieString, name) {
const cookies = parseCookies(cookieString)
return cookies[name] ?? null
}
function deleteCookie(name, path = '/') {
return encodeURIComponent(name) + '=; max-age=0; path=' + path
}
// Демонстрация
const cookieStr = 'session_id=abc123XYZ; lang=ru; theme=dark; user_id=42'
const cookies = parseCookies(cookieStr)
console.log('Все куки:', cookies)
// { session_id: 'abc123XYZ', lang: 'ru', theme: 'dark', user_id: '42' }
console.log('lang:', getCookie(cookieStr, 'lang')) // 'ru'
console.log('theme:', getCookie(cookieStr, 'theme')) // 'dark'
console.log('missing:', getCookie(cookieStr, 'role')) // null
// setCookie — возвращает строку для document.cookie
const newCookie = setCookie('lang', 'en', {
maxAge: 365 * 24 * 60 * 60,
path: '/',
sameSite: 'lax',
})
console.log('\nСтрока для установки куки:')
console.log(newCookie)
// 'lang=en; max-age=31536000; path=/; samesite=lax'
const deletionStr = deleteCookie('session_id')
console.log('\nСтрока для удаления куки:')
console.log(deletionStr)
// 'session_id=; max-age=0; path=/'
// Пример реального использования: язык интерфейса
const userLang = getCookie(cookieStr, 'lang') || 'ru'
console.log('\nЯзык интерфейса:', userLang) // 'ru'Пользователь выбирает язык интерфейса — русский. Через неделю он снова открывает сайт: язык должен сохраниться. Простое решение — куки: небольшие данные, которые браузер хранит и автоматически отправляет на сервер с каждым HTTP-запросом. В отличие от localStorage — сервер их тоже видит.
Куки решают две задачи: сессия (сервер знает, кто авторизован) и настройки (язык, тема, регион). Главное отличие от localStorage — куки уходят на сервер с каждым запросом.
document.cookie ведёт себя необычно: при чтении возвращает все куки, при записи — добавляет одну:
// Чтение — возвращает ВСЕ куки через "; "
console.log(document.cookie)
// "session_id=abc123; lang=ru; theme=dark"
// Запись — добавляет ОДНУ куку (не перезаписывает остальные!)
document.cookie = "username=ivan_petrov"
document.cookie = "lang=ru"
// Теперь обе куки существуют// expires — дата истечения (UTC строка)
document.cookie = "session=abc; expires=Fri, 31 Dec 2025 23:59:59 GMT"
// max-age — время жизни в секундах (предпочтительнее expires)
document.cookie = "token=xyz; max-age=86400" // 1 день
document.cookie = "prefs=dark; max-age=2592000" // 30 дней
// path — куки видна только по этому пути и ниже
document.cookie = "admin=true; path=/admin"
// domain — для какого домена (с поддоменами: .example.com)
document.cookie = "uid=123; domain=.myshop.ru"
// secure — отправлять только по HTTPS
document.cookie = "token=abc; secure"
// samesite — защита от CSRF
document.cookie = "csrf=xyz; samesite=strict"
// strict — только запросы с того же сайта
// lax — разрешает GET при переходе по ссылке (по умолчанию)
// none — всегда (требует secure)Куки с флагом HttpOnly нельзя прочитать через document.cookie. Они устанавливаются сервером и существуют только в HTTP-запросах:
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=StrictЭто защищает от XSS: даже если злоумышленник выполнит JS, он не сможет украсть такую куку.
function setCookie(name, value, options = {}) {
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`
if (options.maxAge) cookie += `; max-age=${options.maxAge}`
if (options.expires) cookie += `; expires=${options.expires.toUTCString()}`
if (options.path) cookie += `; path=${options.path}`
if (options.domain) cookie += `; domain=${options.domain}`
if (options.secure) cookie += '; secure'
if (options.sameSite) cookie += `; samesite=${options.sameSite}`
if (options.httpOnly) cookie += '; httponly'
document.cookie = cookie
}
function getCookie(name) {
const cookies = parseCookies(document.cookie)
return cookies[name] ?? null
}
function deleteCookie(name, path = '/') {
// Устанавливаем прошедшую дату — браузер удаляет куку
document.cookie = `${encodeURIComponent(name)}=; max-age=0; path=${path}`
}
function parseCookies(cookieString) {
if (!cookieString) return {}
return cookieString
.split('; ')
.reduce((acc, pair) => {
const idx = pair.indexOf('=')
const key = decodeURIComponent(pair.slice(0, idx).trim())
const val = decodeURIComponent(pair.slice(idx + 1))
acc[key] = val
return acc
}, {})
}// Сохранить язык интерфейса на 1 год
setCookie('lang', 'ru', {
maxAge: 365 * 24 * 60 * 60,
path: '/',
sameSite: 'lax',
})
// Прочитать при загрузке страницы
const lang = getCookie('lang') || 'ru'
applyLanguage(lang)
// Удалить при выходе из аккаунта
function logout() {
deleteCookie('session_id')
deleteCookie('csrf_token')
window.location.href = '/login'
}Ошибка 1: хранят чувствительные данные в куке без HttpOnly
// Сломано: доступно через XSS-атаку
document.cookie = 'session_token=abc123'
// Исправлено: сессию устанавливает сервер с HttpOnly
// Set-Cookie: session_token=abc123; HttpOnly; Secure; SameSite=Strict
// HttpOnly — JS вообще не может прочитать эту кукуОшибка 2: не указывают path — кука видна не на всех страницах
// Сломано: кука доступна только на /profile и ниже
document.cookie = 'lang=ru' // path по умолчанию — текущий путь
// Исправлено: path=/ — кука видна везде
document.cookie = 'lang=ru; path=/'Ошибка 3: парсят куки через split('=') — ломается при значениях с '='
// Сломано: JWT-токен содержит '=' в base64
// 'token=eyJ...abc=' → split('=') даст ['token', 'eyJ...abc', '']
// Исправлено: используем indexOf('=') для нахождения первого '='
const idx = pair.indexOf('=')
const key = pair.slice(0, idx).trim()
const val = decodeURIComponent(pair.slice(idx + 1)) // всё после первого '='| | Куки | localStorage |
|---|---|---|
| Отправляются на сервер | Да, автоматически | Нет |
| Размер | ~4 KB | 5-10 MB |
| Срок жизни | Настраивается | До явного удаления |
| HttpOnly | Да (недоступны из JS) | Нет |
| Доступность | Браузер + сервер | Только браузер |
Реализация вспомогательных функций getCookie, setCookie, deleteCookie
// Вспомогательные функции для работы с куками
function parseCookies(cookieString) {
if (!cookieString.trim()) return {}
return cookieString
.split('; ')
.reduce((acc, pair) => {
const idx = pair.indexOf('=')
if (idx === -1) return acc
const key = decodeURIComponent(pair.slice(0, idx).trim())
const val = decodeURIComponent(pair.slice(idx + 1))
acc[key] = val
return acc
}, {})
}
function setCookie(name, value, options = {}) {
let cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value)
if (options.maxAge !== undefined) cookie += '; max-age=' + options.maxAge
if (options.path) cookie += '; path=' + options.path
if (options.domain) cookie += '; domain=' + options.domain
if (options.secure) cookie += '; secure'
if (options.sameSite) cookie += '; samesite=' + options.sameSite
// В браузере: document.cookie = cookie
return cookie // возвращаем для демонстрации
}
function getCookie(cookieString, name) {
const cookies = parseCookies(cookieString)
return cookies[name] ?? null
}
function deleteCookie(name, path = '/') {
return encodeURIComponent(name) + '=; max-age=0; path=' + path
}
// Демонстрация
const cookieStr = 'session_id=abc123XYZ; lang=ru; theme=dark; user_id=42'
const cookies = parseCookies(cookieStr)
console.log('Все куки:', cookies)
// { session_id: 'abc123XYZ', lang: 'ru', theme: 'dark', user_id: '42' }
console.log('lang:', getCookie(cookieStr, 'lang')) // 'ru'
console.log('theme:', getCookie(cookieStr, 'theme')) // 'dark'
console.log('missing:', getCookie(cookieStr, 'role')) // null
// setCookie — возвращает строку для document.cookie
const newCookie = setCookie('lang', 'en', {
maxAge: 365 * 24 * 60 * 60,
path: '/',
sameSite: 'lax',
})
console.log('\nСтрока для установки куки:')
console.log(newCookie)
// 'lang=en; max-age=31536000; path=/; samesite=lax'
const deletionStr = deleteCookie('session_id')
console.log('\nСтрока для удаления куки:')
console.log(deletionStr)
// 'session_id=; max-age=0; path=/'
// Пример реального использования: язык интерфейса
const userLang = getCookie(cookieStr, 'lang') || 'ru'
console.log('\nЯзык интерфейса:', userLang) // 'ru'Напиши функцию parseCookies(cookieString), которая принимает строку в формате document.cookie ("key1=val1; key2=val2") и возвращает объект с парами ключ-значение. Значения должны декодироваться через decodeURIComponent.
cookieString.split('; ').reduce((acc, pair) => { const [k, v] = pair.split('='); acc[k.trim()] = decodeURIComponent(v); return acc }, {})