Comments 22
Когда auth-сервис не наш, но мы ему доверяем, там понятно, для чего приседания с refresh- и access-токенами.
Если все наше, то да, refresh-token не обязателен. Но какой-то механизм обновления access-токенов нужен все равно. Мы же не хотим, чтобы эти токены были валидными слишком долго (а то как тогда отзывать у юзера доступ к сервисам?). Можно такое обновление сделать на уровне апи-шлюза (gateway), прозрачно для клиентского кода.
Похоже на довольно сумбурный конспект лекции. Причем сразу на 2 отдельные темы: JWT и SSL.
При каждом запросе пользователя, будь то получение или изменение каких-либо данных, отправляется заголовок
Authorization = `Bearer ${access-token}`
Как же так? Чуть выше было сказано, что токен храним в http-only cookie.
Если злоумышленник получит токен, то сможет расшифровать его, используя функцию base64UrlDecode.
Не расшифровать, а декодировать. Ничего страшного в этом нет. Вы же не храните там важные данные в открытом виде, правда?
Ну и там еще кой-чего по мелочи
Большое спасибо за комментарий, все по делу. Моя первая статья)
"Не расшифровать, а декодировать" - исправил
"Как же так? Чуть выше было сказано, что токен храним в http-only cookie" - тут действительно недоработка с моей стороны,
вижу следующие варианты:
1) access-token храним без http-only, и для записи в header запроса используем js, но тогда создаем уязвимость, что не очень.
2) вовсе не использовать заголовок для передачи access-token, оставляем его cookie
что скажете по поводу 2 варианта?
Если и auth-провайдер, и сервисы - наши (см ветку выше), то нам refresh token на клиенте не нужен. а access token живет в httpOnly cookie, js-код на клиенте его не будет читать, да и вообще знать про него. С точки зрения клиента этот сценарий не отличается от классического session id в кукиз.
Если auth-provider чужой, то refresh-токен храним в кукиз на домене этого провайдера (тоже httpOnly), запрашиваем у него access token и используем в заголовке запросов к сервисам. Этот access token просто держим в памяти, после обновления страницы заново ходим к auth-provider за новым токеном.
Во втором варианте кукиз с refresh-токеном должна быть доступна для кросс-доменного запроса (заголовок Access-Control-Allow-Credentials: true
, а сама кука SameSite=None;Secure). Если это нежелательно, то чуть усложняем. Чтобы получить первый refresh-токен, надо сделать редирект на сайт auth-провайдера. При получении access-токена (и нового refresh-токена) передаем refresh-токен в заголовке запроса к auth-провайдеру. Refresh-токен держим в памяти, никуда не записываем для безопасности. Поэтому такой редирект надо будет делать при каждом обновлении страницы.
Можно придумать и более сложные сценарии, наверное. Но пока незачем.
Если и auth-провайдер, и сервисы - наши (см ветку выше), то нам refresh token
Получается когда токен будет скомпрометирован, мы сможем бесконечно им пользоваться?
запрашиваем у него access token и используем в заголовке запросов к сервисам. Этот access token просто держим в памяти,
"просто держим в памяти" - Тоесть при каждой перезагрузке станицы мы делаем refresh?
Refresh-токен держим в памяти, никуда не записываем для безопасности.
вы наверное имели в виду Access - токен ?
Вообще идея с тем чтобы хранить Access в памяти мне искажаться вполне приемлемой, Спасибо) но это тоже уязвимость, так как данные можно получить. Использовали уже данный подход? на каких проектах?
Получается когда токен будет скомпрометирован, мы сможем бесконечно им пользоваться?
Так же, как и утекшей сессией (в типичном случае с session id в кукиз). Можно предусмотреть разные механизмы защиты - например, периодически инвалидировать сессию и заставлять юзера перелогиниться, делать лимит на число одновременных сессий у юзера.
"просто держим в памяти" - Тоесть при каждой перезагрузке станицы мы делаем refresh?
Да. Это один апи-запрос к auth-провайдеру
> Refresh-токен держим в памяти, никуда не записываем для безопасности.
вы наверное имели в виду Access - токен ?
Access- токен - это само собой, как описано в предыдушем варианте. Я как раз про refresh-токен, чтобы минимизировать риск его компрометации. Именно поэтому в данном сценарии при обновлении страницы придется делать редирект на страницу auth-провайдера, для получения нового refresh-токена.
хранить Access в памяти мне искажаться вполне приемлемой, Спасибо) но это тоже уязвимость, так как данные можно получить
Каким образом их можно получить?
Я как раз про refresh-токен, чтобы минимизировать риск его компрометации.
тоесть нам придется заново авторизоваться каждый раз? ведь после обновления страницы он будет утерян, не совсем понятен этот момент
Каким образом их можно получить?
любой сторонний js код на сайте сможет это прочесть
ведь после обновления страницы он будет утерян
редирект на сайт auth-провайдера выглядит примерно так:
request: GET /login?redirect=https://my-site.com | Host auth.com
response: 302 | Location: https://my-site.com/?refresh-token=AABBCC
То есть при условии, что на домене auth.com у нас есть кука с сессией, редирект туда-обратно осуществялется без участия пользователя.
любой сторонний js код на сайте сможет это прочесть
сторонний код его другими способами может прочитать (пропатчить window.fetch, прочитать из location.query после редиректа, etc).
сторонний код так просто не прочитает переменную, если она в глобальный скоуп не экспортируется прямо или косвенно.
Как же так? Чуть выше было сказано, что токен храним в http-only cookie.
исправил недоработку в статье, спасибо)
Не понял как при авторизации предполагается сделать set-cookie и передать на клиент сразу access и refresh токен.
На сколько я помню, за один раз в заголовке set-cookie можно передать только 1 значение.
Или предполагается делать 2 отдельных запроса к серверу для получения этих токенов?
2 токена мы получаем только при запросе на обновление токенов, например api/refresh
в последующем в запросах на сервер передаем в заголовке только Access .
Также мы должны обезопасить себя от передачи refresh в куках. Потому сморим чтобы pach=api/refresh было у refresh cookie, и на все запросы кроме api/refresh мы ее не отправляли
В ответе может быть несколько заголовков Set-Cookie, каждый выставляет одну куку.
Пожалуйста, не используйте jwt для пользовательских сессий. Это небезопасно. Тема разобрана уже давно.
http://cryto.net/~joepie91/blog/2016/06/19/stop-using-jwt-for-sessions-part-2-why-your-solution-doesnt-work/
https://redis.com/blog/json-web-tokens-jwt-are-dangerous-for-user-sessions/
Используйте jwt между сервисами и только одноразовые (см. статьи). Для пользовательских сессий ничего лучше обычных сессий ещё не придумали
Проблема обычной сессии в том, что каждый запрос создает нагрузку на ваш сервис аутентикации. Решении - в куку с сессией записать дополнительные данные (например, айди пользователя, список прав). И чтобы защищиться от подделки, дополнительно подписываем. И еще нам надо знать, как давно мы эти доп. данные записали (они имеют обыкновение устаревать). В итоге мы изобрели свой формат для того, что уже стандартизировано в JWT.
Вариант 1
В пункте А вешаем замок и отправляем в пункт Б.
По дороге MITM перехватывает сундук, вешает свой замок, отправляет сундук обратно в пункт А.
В пункте А снимают свой замок и снова отправляют ящик в пункт Б.
Сундук снова перехватывает MITM, снимает свой замок и получает доступ к содержимому сундука. ГГВП.
Для того чтобы скрыть своё существование, MITM снова вешает свой замок и посылает сундук дальше в пункт Б.
В пункте Б вешаем еще один замок и отправляем обратно в пункт А.
Однако сундук перехватывает MITM, снимает свой замок и возвращает сундук в Б.
В пункте Б открываем второй замок, извлекаем содержимое.
Итого, А и Б никак не заметили что их хакнули.
Вариант 2 ломается похожим образом, первый шаг: перехватить замок без ключа из А и вместо него отправить в пункт Б свой замок.
В вашем примере между 1 и 2 пунктом, вы упускаете что Б вешает свой замок. Следовательно в 4 пункте у вас будет замок от Б и вы нечего не откроете
JSON Web Token и Secure Sockets Layer