Comments 39

Заминусовали зря, но статья предлагает решение в разделе "Явное аннулирование токена пользователем"

В своём комментарии я возражал против процедуры (минусования) и поддержал автора комментария. Ну и формально ответил на его вопрос. А что до stateless, то да, абсолютно полная задница с ним.

Кто может объяснить, где правильно (с точки зрения безопасности) хранить JWT токены — access и refresh?
в localStorage? session? cookie?
А какая разница, где хранить строку, которая и так при каждом запросе в открытом виде отправляется?

Одно дело по https отправляется. А другое дело хранится в браузере. Вредоносное расширение может получить ключи?

Такое «вредоносное расширение», которое имеет доступ к локальным данным сайта, может просто взять и отправить запрос от вашего имени прямо на месте.
Если для вас вопрос утечки токена — это большая проблема, то стоит пересмотреть архитектуру, и в частности целесообразность использования JWT вообще.
1. просто взять, отправить запрос от моего имени, и отправить вместе с токеном — это разные вещи. получается localStorage это никакая не песочница для сайта? нет изоляции вообще? кто угодно может пойти и выдернуть что хочет?

2. проблема не в JWT — любой пароль меняется на условный токен, который может быть кукой, сессией и т.д.
я не говорю про физический доступ к компьютеру со стороны злоумышленника.

а вполне обычный кейс, человек поставил скомпрометированое расширение, или открыл вредоносный сайт в соседней вкладке.

получается токены могут быть успешно получены из соседней вкладки?
Как раз наоборот, ВСЁ вами описанное — песочница сайта, прикрытая CORS-политиками. В этом плане они абсолютно равнозначны. Чтобы считать их, нужно в эту песочницу пролезть. А раз пролез, то зачем тащить что-то наружу, если можно пользоваться прямо на месте?

Ну не совсем так.


Куки прикрываются именно что CORS-политиками, а их можно обойти если отправлять простой запрос, не выходящий за рамки возможностей HTML формы.


А вот localStorage защищена уже Same Origin песочницей, и туда можно пробраться только через XSS.

Если можешь себе позволить не полное спа, то куки, иначе без httponly это всё равнозначно.
Пожалуй, максимально безопасно будет сделать так: http-only кука, на сервер ходить по https. В самом токене хранить только самые базовые значения (например, только логин пользователя и fingerprint), а уже на сервере решать, что именно этот пользователь может делать. В примере в статье есть поле
"admin":true
, это выглядит не слишком безопасно. А идея добавлять случайные строки вполне катит. Ах да, и ограничивать токен по времени, активно использовать revoke/refresh политики. А еще CORS и все такое. Тогда атаки типа man-in-the-middle почти не страшны.
В самом токене хранить только самые базовые значения (например, только логин пользователя и fingerprint), а уже на сервере решать, что именно этот пользователь может делать.
А чем это тогда отличается от обычной сессии?
Вопрос, на который я отвечал подразумевает, что хранение происходит на клиенте, который является браузером. Там разница, действительно, не слишком существенная. За исключением того, что, пожалуй, дополнительное шифрование никогда никому не мешало в плане безопасности. JWT становится действительно оправданным и выигрышным на масштабе, особенно при использовании микросервисов. Там и payload может быть больше и содержать более развернутую информацию, и вот вам единый формат для всех сервисов (в том числе и для браузеров). И вопрос JWT на сервере стоит не так остро.
А чем это тогда отличается от обычной сессии?

stateless и прочие плюшки токенов сохраняются.

Вот тоже не понимаю, почему логика должна зависеть от того что лежит в токене.
По идее там должны быть только «read-only» значения для клиента. На бэкенде мы все равно в большинстве случаев определяем юзера по этому токену и подтягиваем его данные из базы или какого-нибудь сервиса (с ролями/правами и т.д.)
а бэкенде мы все равно в большинстве случаев определяем юзера по этому токену и подтягиваем его данные из базы или какого-нибудь сервиса (с ролями/правами и т.д.)


Представьте, у вас микросервисная архитектура. Один из сервисов предоставляет премиальную услугу (некую информацию, которую могут получить только премиальные пользователи). Этот сервис ничего не знает о пользователях и не имеет связи с базой данных пользователей (зачем?). Вместо этого этот сервис может напрямую из токена взять права или роль пользователя и определить — может ли данный пользователь получать данную информацию. Причём данный сервис может делать запросы еще к каким-то сервисам (которые в свою очередь тоже могут определять, что можно показать, а что нельзя опять же по данным токена). Соответственно, в такой схеме мы избегаем избыточных запросов к базе пользователей. Конечно, вы скажете, запросы можно кэшировать и нагрузка не будет сильно высокой — но вот видите, уже добавляется очередная задача, которой можно было избежать.
Согласен, валидный юзкейс. Но все-таки есть куча микросервисных архитектур где можно не передавать sensitive данные в токене, что не делает само использование jwt бесполезным.
поле «admin»:true
ну какие-то клеймы же можно положить в токен, чтобы на фронте отображать/скрывать контент для админа или обычного пользователя и другие кейсы делать.

а на бэке все равно проверять права, читать из токена это не секьюрно. даже если они подписаны.
а на бэке все равно проверять права, читать из токена это не секьюрно. даже если они подписаны.

Если несекьюрно брать права из подписанного токена, то тогда оттуда несекьюрно брать вообще всё. Ибо, если представить, что некий злоумышленник может подписать токен, то этот кто-то может представиться и любым пользователем вообще. А следовательно токену нет доверия от слова совсем. Вы это хотели сказать?
я имел в виду, права могут изменится, доступы.
а токен к примеру живет долго.

понятное дело что по хорошему — при изменении прав/доступов и т.д. надо перевыпускать токен, а старый отправлять в черный список.
Access Token не должен жить долго. Вот Refresh Token — да, но он права не определяет. Старый токен отправить в черный список нельзя, так как тогда надо вести историю вообще всех выданных на данный момент токенов, а это в свою очередь лишает смысла использование JWT.
Access Token не должен жить долго

для вредоносного скрипта даже 10 сек хватит сделать все.

Старый токен отправить в черный список нельзя

как раз практика говорит о том, что рефреш токен когда перевыпускается — старый прибивается. ибо он не нужен нигде, ни на фронте, ни на беке.

а Access Token добавляют в черный список когда делают блокировку токенов, например пользрователь руками в личном кабинете делает выход со всех устройств, кроме текущего, тогда система добавляет все Access Token'ы в черный список.

За эти 10 секунд никто не успеет выдать пользователю права, а потом забрать их.

для вредоносного скрипта даже 10 сек хватит сделать все.

Хватит. А хватит ли 10 секунд, чтобы обнаружить компрометацию учетной записи/токена, перенастроить права и заблеклистить токен?

Сама идея Access/Refresh токенов как раз и базируется на том, что мы доверяем содержимому подписанного Access-токена и потому он должен быть коротко живущим. А вот Refresh-токен мы используем только для получения нового Access-токена. И ему мы доверяем только в плане аутентификации пользователя (но не авторизации!). И так как для удобства Refresh-токен является долгоживущим, там и нужна возможность блокировки.

как раз практика говорит о том, что рефреш токен когда перевыпускается — старый прибивается. ибо он не нужен нигде, ни на фронте, ни на беке.

Я вел речь об access токене — это ведь из контекста нашей дискуссии ясно. Что касается refresh токенов, то черные списки могут и должны быть, но только и там никто не ведет учёт всех выданных токенов.

Прочитал статьи эти. Один момент смутил — автор говорит, что из localStorage любой javascript может получить данные. Но вроде же можно только с того же домена читать localStorage? Наоборот, если пользователь зайдет на вредоносный сайт, то он сможет какой-нибудь запрос на https://sberbank.ru с правильными куками отправить, а вот ничего из localStorage относящегося к sberbank.ru он использовать не сможет, разве не так?

Вы понимаете все верно. Куки подвержены CSRF, а localStorage подвержен XSS.


Автор статьи подразумевает, что бороться с CSRF значительно проще, потому что он изучен на десяток лет больше, и в принципе закрывается csrf токеном. В то же время XSS значительно сложнее для защиты — к примеру каждый новый SPA Framework может иметь свою дыру.

Перехват токенов

Непонятный пункт.
Если предположить что клиент это браузер, то зачем вообще JWT, если можно кукисы использовать?

PS: Ни разу не слышал о таком механизме защиты от «перехвата токена» (есть OAuth2.0 Proof-of-Possession, но не похоже что его широко используют в данный момент).
Не путайте теплое с мягким. Кукисы — это метод хранения/доставки информации, а JWT — метод ее кодирования (шифрования).
Если у автора это просто «метод кодирования», то в чем вопрос «безопасности метода кодирования»?
И причем тут «перехват» (который как раз угроза доставки\хранения)?

Автор явно замахивался на что-то большее чем рассмотрения «метод кодирования». JWT например является частью эко-системы OpenID Connect, который подразумевает что клиент не всегда браузер, а значит надежный cookie management может быть недоступен.

PS: В session-кукисах JWT использовать безусловно можно (вот Play 2 это делает по умолчанию), вопрос целесообразности только.
Суть JWT не в том, что мы заменяем им id сессии в куках. Суть в том, что в токене есть вся необходимая для выполнения запроса информация о пользователе (user_id, роли, например) и сервис, обрабатывая запрос, не лезет в базу пользователей на каждый чих.

А как передать этот токен с запросом и где хранить на клиенте — каждый решает сам, факторов много (вдруг общение по web-сокетам идёт, какие тогда тут куки).
По поводу фингерпринта и перехвата токена через xss: как я понял, подразумевается, что сам токен хранится в каком-то хранилище, которое доступно из js.
Тогда мне не очень очевидно: чем это лучше хранения самого токена в cookie с HttpOnly Secure SameSite?

Куки подвержены CSRF-атаке, а потому их надо дополнительно защищать анти-CSRF-токеном. Который, в свою очередь, может быть угнан через XSS.


Таким образом, куки не дают надежной защиты от XSS, они просто увеличивают сложность атаки.

Only those users with full accounts are able to leave comments. Log in, please.