LocalStorage vs Cookies: все, что нужно знать о безопасном хранении токенов JWT во Front-End
В статье даются рекомендации по хранению и использованию JWT токенов. Если кратко резюмировать статью, то в ней рекомендуется полученные от бекенда access токены хранить в приложение, а refresh токены в куках. А вот если хотите узнать почему именно такие рекомендации читайте статью.
Оригинальная статья: Michelle Wirantono — LocalStorage vs Cookies: All You Need To Know About Storing JWT Tokens Securely in The Front-End
Токены JWT замечательны, но как их надежно хранить на фронтенде? Мы рассмотрим плюсы и минусы LocalStorage и cookie для хранения JWT.
Краткий обзор Access token и Refresh token
Токены доступа (Access token) обычно являются недолговечными токенами JWT, подписанными сервером и включенными в каждый HTTP-запрос к серверу для авторизации запроса.
Токены обновления (Refresh token) обычно представляют собой долговечные зашифрованные токенами JWT, хранящиеся в базе данных, которые используются для получения нового токена доступа по истечении срока его действия.
Где лучше хранить свои токены?
Существует два распространенных способа хранения токенов: в localStorage и в cookie. Есть много споров о том, какой из них лучше, но большинство людей склоняются к cookie из-за большей безопасности.
Давайте рассмотрим это сравнение чуть более подробнее.
Local Storage
Плюсы: Это удобно.
- Это чистый JavaScript и с ним удобней работать. Если у вас нет бэкэнда и вы полагаетесь на стороннее API, вы не всегда сможете установить определенные cookie для вашего приложения.
- Работает с API-интерфейсами, которые требуют, чтобы вы поместили свой токен доступа в заголовок запроса, типа такого Authorization Bearer ${access_token}.
Минусы: Такой способ уязвим к XSS атакам.
Атака XSS происходит, когда злоумышленник может запустить свой скрипт JavaScript на вашем сайте во время посещения его другим пользователем. Это означает, что злоумышленник сможет получит доступ к токену доступа, который вы сохранили в localStorage.
Cookies
Плюсы: Файл cookie может быть не доступен через JavaScript, поэтому он не так уязвим для атак XSS, как localStorage.
- Если вы используете флаги httpOnly, то cookie будут не доступны из JavaScript. Это означает, что даже если злоумышленник сможет запустить JS на сайте, он не сможет прочитать ваш токен доступа.
- Cookie автоматически отправляется в каждом HTTP-запросе на ваш сервер.
Минусы: В зависимости от варианта использования иногда вы не сможете хранить свои токены в файлах cookie..
- Размер файлов cookie ограничен 4 КБ, поэтому, если вы используете большие токены JWT, сохранение в файле cookie станет не возможным.
- Существуют сценарии, когда вы не можете использовать cookie напрямую для доступа к серверному API. Например когда требуется наличие токена в заголовке запроса.
Кратко о XSS атаках
Итак мы уже сказали что local storage уязвим, так как он легко доступен с помощью JavaScript, и злоумышленник может получить access token и использовать его. Однако, хотя cookie с httpOnly недоступны из JavaScript, это не означает, что с помощью cookie вы полностью защищены от XSS атак.
Если злоумышленник может запустить JavaScript в вашем приложении, он все равно сможет отправить HTTP-запрос на ваш сервер получив таким образом все токены. Но для злоумышленника это менее удобно, потому что он не сможет прочитать содержимое токена, хотя это и не всегда нужно.
Cookies и CSRF Атаки
CSRF Attack — это атака, которая позволяет сделать запрос от имени пользователя но без его ведома. Например, если веб-сайт принимает запрос на изменение электронной почты через такой запрос:
POST /email/change HTTP/1.1 Host: site.com Content-Type: application/x-www-form-urlencoded Content-Length: 50 Cookie: session=abcdefghijklmnopqrstu email=myemail.example.com
То злоумышленник может легко создать форму на вредоносном веб-сайте, которая отправит POST запрос на https://site.com/email/change со скрытым полем электронной почты, и cookie с токенами будут автоматически включены в этот запрос.
Однако это можно легко исправить, используя флаг cookie sameSite и добавив anti-CSRF token.
Вывод
Хотя куки все еще имеют некоторые уязвимости, они предпочтительнее по сравнению с localStorage, когда их применение возможно. И вот почему?
- Как localStorage, так и cookie уязвимы для атак XSS, но злоумышленнику сложнее выполнить эту атаку, когда вы используете cookie с httpOnly.
- Cookie уязвимы для атак CSRF, но их вероятность можно уменьшить с помощью флага sameSite и токенов защиты от CSRF (anti-CSRF tokens).
- Вы все еще можете использовать cookie, даже если вам нужно использовать заголовок Authorization: Bearer или ваш JWT больше 4 КБ. Это также согласуется с рекомендацией сообщества OWASP:
Не храните идентификаторы сеанса в localStorage, так как данные всегда доступны через JavaScript. Файлы cookie могут снизить этот риск, используя флаг httpOnly.
OWASP: HTML5 Security Cheat Sheet
Итак, как мне использовать куки для сохранения моих токенов OAuth 2.0?
Напомним, вот несколько способов хранения ваших токенов:
- Вариант 1: Хранение Access Token в localStorage: но это выбор уязвим к XSS атакам.
- Вариант 2: Хранение Access Token в cookie с httpOnly: но этот выбор будет уязвим к CSRF атакам, хотя его можно уменьшить, что лучше чем XSS.
- Вариант 3: Хранение Refresh Token в cookie c httpOnly: это будет безопасно от CSRF атак, и немного лучше от XSS.
Мы рассмотрим, Вариант 3, так как он предлагает лучшие преимущества из трех вариантов.
Сохранять Access Token в памяти приложения а Refresh Token в cookie.
Шаг 1: Пользователь получает Access Token и Refresh Token когда проходит аутентификацию.
После аутентификации пользователя Сервер авторизации отдает пользователю access_token и refresh_token. Access_token может быть включен в тело ответа, refresh_token должен быть включен только в cookie.
Свойства токена в cookie:
- Используйте флаг httpOnly, чтобы из JavaScript не было возможности прочитать его.
- Используйте флаг secure = true, чтобы его можно было отправлять только через HTTPS.
- По возможности используйте флаг SameSite = strict, чтобы предотвратить CSRF атаки. Но это можно использовать только в том случае, если у сервера авторизации тот же домен, что и у клиентского приложения. Если это не так, ваш Сервер авторизации должен установить нужные заголовки CORS в серверной части или использовать другие методы, чтобы гарантировать, что запрос на обновление токена может быть выполнен только авторизованными веб-сайтами.
Шаг 2: Сохраните Access Token в памяти
Хранение токена в памяти означает, что вы поместили этот токен доступа в соответствующую переменную в своем приложение. Да, это означает, что access token исчезнет, если пользователь переключит вкладки или обновит страницу. Вот зачем у нас есть refresh token.
Шаг 3: Обновляйте access token, используя refresh token
Когда access token token пропадет или срок его действия истечет, используйте серверное API для получения нового токена используя refresh token, который был сохранен в cookie в шаге 1, и будет включен в каждый запрос (то есть сервер получит его через cookie ). После получения нового access token сможете использовать его для своих запросов API.
Это означает, что ваш токен JWT может быть больше 4 КБ, и вы также можете поместить его в заголовок авторизации.
Заключение
Если вы создаете механизм авторизации для своего веб-сайта или мобильного приложения, эти статьи могут помочь:
- What On Earth Is OAuth? A Super Simple Intro to OAuth 2.0, Access Tokens, and How to Implement it in your Site
- Passwordless Login with Email and JSON Web Token (JWT) Authentication using Next.js
- Here’s How to Integrate Cotter’s Magic Link to Your Webflow Site in Less Than 15 minutes!
Спасибо
Отличная статья. Наконец-то где-то нормально описано