Каковы преимущества и недостатки хранения JWT в cookie по сравнению с Authorization header?

Ответ

Выбор между хранением JWT в cookie и передачей через заголовок Authorization — это компромисс между удобством и безопасностью, в частности, защитой от XSS и CSRF атак.

Хранение в cookie

Токен автоматически отправляется браузером с каждым запросом на тот же домен.

  • Плюсы:

    • Защита от XSS: Если установить флаг HttpOnly, токен становится недоступным для JavaScript на клиенте, что является основной защитой от кражи токена через XSS-уязвимости.
    • Простота: Не требует дополнительного кода на клиенте для прикрепления токена к запросам.
  • Минусы:

    • Уязвимость к CSRF (Cross-Site Request Forgery): Поскольку браузер отправляет cookie автоматически, злоумышленник может заставить пользователя выполнить нежелательное действие на вашем сайте со стороннего ресурса. Для защиты необходимо использовать SameSite атрибут (Strict или Lax) и/или CSRF-токены.
# Пример установки защищенного cookie в Flask
response.set_cookie(
    'access_token',
    value=jwt_token,
    httponly=True,  # Защита от XSS
    secure=True,    # Отправка только по HTTPS
    samesite='Lax'  # Защита от CSRF
)

Хранение в localStorage / sessionStorage и передача в Authorization header

Токен хранится в веб-хранилище браузера и вручную прикрепляется к каждому API-запросу.

  • Плюсы:

    • Защита от CSRF: Токен не отправляется автоматически, поэтому CSRF-атаки невозможны.
    • Гибкость: Легко работает с разными доменами и поддоменами. Является стандартом для API, используемых мобильными и нативными приложениями.
  • Минусы:

    • Уязвимость к XSS: Если на сайте есть XSS-уязвимость, злоумышленник может выполнить JS-код, который украдет токен из localStorage и отправит на свой сервер.
    • Требует реализации: Необходимо писать код на клиенте (например, interceptor в Axios) для добавления заголовка Authorization: Bearer <token> ко всем защищенным запросам.
// Пример отправки запроса с токеном из localStorage
const token = localStorage.getItem('jwt_token');

fetch('/api/data', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  }
});

Рекомендация:

  • Для традиционных веб-приложений (server-side rendering) предпочтительнее использовать cookie с флагами HttpOnly, Secure и SameSite=Strict.
  • Для Single Page Applications (SPA) и мобильных клиентов чаще используется Authorization header. Для повышения безопасности в SPA рекомендуется хранить токен в памяти приложения, а не в localStorage, чтобы минимизировать окно для XSS-атак.

Ответ 18+ 🔞

А, ну это же классический спор, блядь, про куки и заголовки! Вечная тема, как "Муму" у Тургенева, только про токены, ёпта. Слушай, сейчас разложу по полочкам, чтобы ты не был, как Герасим, немой и в замешательстве.

Вариант первый: Сунуть токен в куки.

Это как если бы ты, блядь, прибил табличку "Муму" к собаке на ошейник, и она сама везде с ней ходит. Браузер её автоматом таскает в каждый запрос на твой домен.

  • Что хорошего, ёбушки-воробушки?

    • От XSS защищает, как танк. Ставишь флажок HttpOnly — и всё, пиши пропало! Никакой твой кривой JavaScript до этого токена не доберётся, хоть ты тресни. Кража через XSS — в пизду.
    • Проще некуда. На клиенте нихуя не надо париться, не надо писать код, который этот токен цепляет. Всё само летит.
  • А где подвох, хитрая жопа?

    • CSRF, блядь, вылезает. Раз куки сами летят, значит, злоумышленник может с левого сайта заставить браузер юзера наделать делов на твоём. Как защищаться? Ну, SameSite атрибут в помощь (Strict или Lax), или ещё CSRF-токены городить.
# Вот так, например, во Flask эту защищённую печеньку запихивают
response.set_cookie(
    'access_token',
    value=jwt_token,
    httponly=True,  # Руки прочь, JavaScript! Защита от XSS.
    secure=True,    # Только по HTTPS, ёпта, не по этому вашему HTTP.
    samesite='Lax'  # Чтоб от CSRF хоть как-то
)

Вариант второй: Запихнуть в localStorage и таскать в заголовке Authorization.

Это уже как если бы Герасим носил Муму в мешке за пазухой и только по надобности доставал, показывал и говорил "Му!". Хранишь сам, отправляешь сам.

  • Чем это, блядь, круто?

    • CSRF похуй. Токен сам по себе не летит, значит, заставить его улететь со стороннего сайта — нихуя не выйдет. Защита от этой хуйни — наше всё.
    • Гибкость — овердохуища. Легко работает с кучей доменов, поддоменов. Это ж стандарт де-факто для API, которые ещё и в мобилках крутятся.
  • А чем тогда, сука, плохо?

    • XSS тут как тут, пиздец. Нашёл злодей дыру, выполнил свой скрипт — и всё, токен из localStorage вытащил и себе на сервак отправил. И прощай, безопасность.
    • Надо городить. На клиенте теперь обязан писать код, который к каждому запросу этот заголовок прилепляет. Interceptor в Axios или свою обёртку над fetch.
// Смотри, как это обычно выглядит. Достал и прицепил.
const token = localStorage.getItem('jwt_token'); // Достаём из того самого уязвимого места

fetch('/api/secret-data', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${token}`, // Вручную прилепил
    'Content-Type': 'application/json'
  }
});

Так что в итоге, ёпта? Рекомендация, блядь:

  • Если у тебя обычное веб-приложение, где страницы на сервере рендерятся — бери куки (HttpOnly, Secure, SameSite=Strict). Безопасно и просто, как топор Герасима.
  • Если пишешь SPA (одностраничное приложение) или делаешь API для мобилок — тут чаще заголовок Authorization. А чтобы XSS не съел, в SPA можно токен вообще в памяти приложения держать, а не в localStorage валять. Сложнее, но безопаснее. Волнение, блядь, чувствую, ты уже понял.