Комментарии 53
Токены стоит сохранять только в HttpOnly cookie.
Почему же? Разве хранение токена в local storage + защита от CSRF не является безопасным вариантом?
Извините, тут не выйдет использовать HttpOnly куки.
Но я про то, что любое расширение может читать обычные куки и localstorage. В xss внедренный код тоже.
CSRF спасает от выполнение со стороннего сайта, а не от кражи куки.
Токены стоит сохранять только в HttpOnly cookie.
Это шутка такая? Чем он тогда будет отличаться от обычного идентификатора сессии?
Как же людям маркетологи голову запудрили с этими токенами. Сначала пихают их куда только возможно, а потом пытаются из них сделать более логически верное и безопасное решение — то есть сессию.
Возникает вопрос а зачем же огород городить было. Опять же про jwt. Применение jwt позволяет масштабировать приложение т.к. учетные данные клиента не хранятся в сессии и не перезапрашиваются при каждом запросе в базе данных. Следовательно api можно рассшаривать на несколько физических серверов не боясь той проблемы что логин клиента будет на одном сервере а запрос к api придет на другой сервер.
Но согласен с Вами в той части что я часто встречаю реализацию jwt которая не использукт ни возможность секьюрности ни возможность масштабирования. То есть токены выдает тот же сервер на котором работает api и в токена хранится только id клиента по которому каждый раз запрашивается база данных.
Это уже чистыймаркетинг
Как минимум даже такое испольщование jwt это готовность к масштабированию без изменения фронта.
А в остальном, особенно если тот же jwt выдает не специализированный сервис а то же самое веб приложение то конечно по секьюрности все один к одному.
Наверное у тредиционной сессии секьюрность даже побольше в данном случае. ID сессии но идее может меняться на каждый запрос, а JWT, как вы правильно заметили, нет.
Почему бы не закодировать все сразу в одном токене? Посмотрите как работает jwt, в нем все реализовано довольно логично и понятно.
Ни в OIDC, ни в OAuth 2 нет ограничений на то, что лежит в access token, так что там может быть и JWT (и, например, IdentityServer так и делает, если поставить "значимые токены").
Во-первых, то, что вы описали, не противоречит использованию refresh-токенов. Во-вторых, эта схема подразумевает, что у вас нет отзывов… что не всегда так.
На самом деле, так как вы написали делать конечно не надо. Если мы «доверяем» JWT настолько, что готовы не лазить в хранилища и пользоваться всеми благами stateless серверов, то чтобы анулировать JWT достаточно в него же положить expired дату. После расшифровки ее прочитать и не пускать дальше. Ну а получить новый JWT юзер должен уметь в любой вообще момент времени, даже если его текущий токен не просрочился. И конечно же хранить сам JWT на серверах нельзя, stateless же.
Что касается реальных приложений я действительно пока еще не юзал токены со сроком действия (веренее юзал для временных токенов) А клиенту после логина сообщал клал в токен только его идентификатор. После чего чтобы не лазить в базу хранил юзера в редисе со сроком действия и перезапрашивал в базе по истечению срока. В основном это было сделано исключительно из за того что согласовать с мобильными разработчиками рефреш токена было достаточно неперспектовной идеей. Т.к. если бы я их даже уговорил, добиться беспроблемной ротации при отстутсвии хорошо отлаженной билиотек, которая делала бы примерно то о чем эта статья сверху — было бы очень долго и не факт что не было бы сбоев у клиентов.
Я писал чисто про JWT, как про криптографию для доверенного хранения данных на клиенте между запросами. Он особо не говорит как именно должен проходить процесс авторизации.
Вот именно поэтому утверждение "Jwt вроде не рефрешат с помощью рефреш токена" и ошибочно.
С другой стороны, а как вы его будете отзывать?
Как обычно — держать список валидных, и проверять в нем.
Да и какой смысл в JWT, если при каждом запросе все равно надо сверяться с базой?
Большой. Сервер может сделать большую часть проверок еще до необходимости уточнять на отзыв, что экономит обращения к базе для протухших и других невалидных токенов, а обращения за отзывом делать с кэшированием на то время, на которое допустима задержка отзыва (да и банально обращение "есть такой токен или нет" быстрее, чем "прочитать-разобрать"). Клиент же, в свою очередь, получает понятный ему токен, который он может анализировать и делать выводы.
Судя по характерным признакам, речь идет про OAuth 2. Речь именно о нем, или о каком-то своем велосипеде?
1 я могу не ходить в базу данных за учётной записью пользователя т.к. в токена пришла актуальная информация которая отстаёт максимум на время действия токена
2 многие утверждают что это более безопасно хотя я не уверен. Во всяком случае выглядит более секьюрно
Доступ с двумя токенами это совсем не обязательно oauth2
Я знаю, что это не обязательно oauth, поэтому и решил сначала уточнить, что же это.
если просто клиенту отдать после какого то подтвкрждения два токена например jwt то мы имеем два положительных результата
И один (важный) отрицательный: мы опять пишем свой собственный велосипед в области аутентификации/авторизации, чего лучше избегать.
На стороне сервера все уже готово в технологии jwt.
Не, совсем не все. В "технологии JWT" описано, что делать в случае кражи токена, или это все-таки просто "a compact, URL-safe means of representing claims to be transferred between two parties"? А то там написано, знаете ли, что "The contents of a JWT cannot be relied upon in a trust decision unless its contents have been cryptographically secured and bound to the context necessary for the trust decision."
Насколко я понимаю общепринятого клиента для ротации тоаенов пока нет.
Если речь об OAuth 2, то клиентская часть там стандартизирована, и я встречал ее имплементации в виде промежуточных хэндлеров. Другое дело, что всегда есть вопрос "что делать, если рефреш тоже протух".
Во-первых, он прекрасно экспайрится, он не бессрочный.
Во-вторых, он может быть отозван.
В-третьих, он может быть кем-то использован, и, как следствие, тоже перестать быть валидным.
(в-четвертых он не обязан быть одноразовым)
Во-первых, он прекрасно экспайрится, он не бессрочный.
С чего вы это взяли? Тогда он практически теряет свой смысл
Во-вторых, он может быть отозван.
Это конечно. Более того, он «отзывается» при каждом использовании. Поэтому без реальных реквизитов надолго получить доступ к юзеру не получится.
В-третьих, он может быть кем-то использован, и, как следствие, тоже перестать быть валидным.
Одноразовость как раз и делает его практически бесполезным для кражи. Можно воспользоваться один раз и получить временный токен. Далее юзер опять авторизуется. Выданный рефреш обнуляется, а access отзывается.
(в-четвертых он не обязан быть одноразовым)
Я вас ни к чему не обязываю вообще. Просто это наиболее рабочая схема.
С чего вы это взяли?
RFC 6749 (OAuth 2) 5.2 Error Response: "invalid_grant
[...] refresh token is invalid, expired, revoked".
На практике как минимум Azure AD выдает рефреш-токены со сроком жизни дней в 90.
Тогда он практически теряет свой смысл
Нет, не теряет. Просто мы контролируем доступ в долгосрочной перспективе.
Более того, он «отзывается» при каждом использовании.
Не обязательно. "The authorization server MAY revoke the old refresh token after issuing a new refresh token to the client."
Одноразовость как раз и делает его практически бесполезным для кражи. Можно воспользоваться один раз и получить временный токен.
… получить access token и новый refresh token. Предыдущее приложение (может быть) потеряло доступ, приложение атакующего получило доступ. Дальнейшее зависит от приличного сочетания настроек.
(это одна из причин, почему refresh token делают ограниченным по времени)
RFC 6749 (OAuth 2) 5.2 Error Response: «invalid_grant [...] refresh token is invalid, expired, revoked».
И что тут написано? Что так можно делать, да. Я не говорил что так нельзя делать. Я говорил не нужно.
На практике как минимум Azure AD выдает рефреш-токены со сроком жизни дней в 90.
Для вас MS — это образец как нужно? Это не тот ли Azure на котором еще 4 года назад было практически невозможно поднять linux-сервер?
Нет, не теряет. Просто мы контролируем доступ в долгосрочной перспективе.
В целом, если очень хочется сделать его просроченным по времени, тогда можно и заложить какой-то большой промежуток, да. Но главное все же чтобы он был одноразовым. Иными словами, если даже срок не вышел, но рефреш токен был использован, он автоматом должен анулироваться. Временное ограничение — это скорее дополнение.
Не обязательно. «The authorization server MAY revoke the old refresh token after issuing a new refresh token to the client.»
Вы воспринимаете то, что я говорю слишком мандаторно. Я говорю как лучше делать, а не то, что сделать по-другому невозможно. Мы с вами даже переписываемся в комментариях к статье со своим велосипедом. Сделать можно что хочешь, вопрос в необходимости.
… получить access token и новый refresh token. Предыдущее приложение (может быть) потеряло доступ, приложение атакующего получило доступ. Дальнейшее зависит от приличного сочетания настроек.
Да, приложение потеряет доступ и будет показывать форму логина. Юзер введет логин, получит новые токены, а злоумышленник останется с носом. Именно то, что рефреш одноразовый делает очевидным факт его кражи. Если кто-то украл его и воспользовался, юзера сразу «выкинет» из системы, а не в тихую будет продолжать работать.
И что тут написано? Что так можно делать, да. Я не говорил что так нельзя делать. Я говорил не нужно.
Вы спросили, с чего я это взял. Вот с этого и взял.
Для вас MS — это образец как нужно?
Для меня это образец, как есть, и к чему надо быть готовым разработчикам клиентской части.
Иными словами, если даже срок не вышел, но рефреш токен был использован, он автоматом должен анулироваться.
Как уже говорилось, на это есть разные взгляды.
Вы воспринимаете то, что я говорю слишком мандаторно. Я говорю как лучше делать, а не то, что сделать по-другому невозможно.
Так не важно, что вы (или я) думаете, как лучше, важно, что нам говорит спецификация. Спецификация говорит, что токен может не отзываться (и надо заметить, что из нее не очевидно, обязан ли в этом случае старый токен быть в ответе), поэтому клиент должен быть готов и к такой ситуации.
Юзер введет логин, получит новые токены, а злоумышленник останется с носом.
Для этого вам надо при логине пользователя инвалидировать все ранее выданные ему токены. Это точно удобное и желаемое поведение?
И опять возникает вопрос с асинхронностью. Отправлены два запроса. Один до истечения срока другой после. Второй запрос обгоняет первый в сети. И мы аннулируем оба токена. Первый запрос приходит и не проходит. Или другой вариант. Два запроса обнаружили что тока истек и оба рефрешат их одним рефреш товаром. Результат тот же отрицательный.
1. Токен был запрошен с клиента и аннулирван сервером после чего запрос клиенту был отправлена новая пара токенов. Запрос до клиент ане дошел из-за сетевых проблем и теперь у клиента есть просроченный акцес с токен, аннулированный рефреш токен и нет возможности их обновить. Никакой
2. То же самое но теперь из-за медленной работы сети клиент после таймаута сделал дублирующий запрос (это например так устроен клиент REST на Андроиде) Первый из запросов клиент сбросил а второй запрос заканчивается с ошибкой т.к. идет повторное обращение с одним и тем же рефреш токеном.
3. Просто два конкурирующие запроса которые вычислили что нужно обновить токены и все отправили запросы — в результате токен обновился один первый а все остальные завершились с отказом и с ошибкой
Когда говорится о защищенности рефреш токена то имеется в виду то что Вы его не присылаете на сервер приложения а обмениваетесь им по гарантированно защищенному каналу с сервером токенов. Прямая аналогия это то что вы не присылаете в интернет-магазин номер своей платежной карточки. Если его кроме всего прочего не сохранять в сторах которые могут быть скомпрометированы или в глобальных переменных которые могут быть прочитаны то украсть его модно только вместе с устройством.
Гм. Это OAuth 2 Resource Server, или это какая-то своя авторизация?
Если это OAuth 2, то есть несколько нюансов.
Во-первых, обновление токена может требовать (и часто требует) client credentials, которые у вас не фигурируют.
Во-вторых, в браузере намного чаще используется Implicit Grant, в котором refresh token не выдают (в частности, потому, что в браузере сложно выполнить требование "Refresh tokens MUST be kept confidential in transit and storage, and shared only among the authorization server and the client to whom the refresh tokens were issued.")
Логичней будет ввести метод getToken, унести в него всю логику работы с токеном и использовать его в fetchWithAuth.
Если токен есть и он не expired — getToken вернёт уже resolved промис с токеном, а если токен надо обновлять — вернёт промис на refresh. Этот промис надо сохранить и возвращать его всегда если он в статусе pending — таким образом любое количество API-запросов инициирует только один запрос на refresh.
Только нативные промисы не дают узнать свой статус. Поэтому или колхозить некий свой статус промиса, или использовать drop-in альтернативы типа bluebird.
Функция для обновления токена
const newToken = await refreshToken(tokenData.refresh_token); // если истек, то обновляем токен с помощью refresh_token saveToken(newToken);
По-моему кроме обновления самого токена, ваша функция должна ТАКЖЕ обновлять и refresh-token.
if (Date.now() >= tokenData.expires_on * 1000) { // проверяем не истек ли срок жизни токена
Разве можно полагаться на Date.now()? Если «хакер» подменит время в системе?
Хранить данные для авторизации мы будем в sessionStorage или localStorage, в зависимости от наших нужд. В первом случае данные хранятся до тех пор, пока пользователь не завершит сеанс или не закроет браузер
Какой смысл хранить токены в sessionStorage? Почему не в самом клиентском коде? Раз уж он будет доступен только на время жизни клиентского приложения.
По-моему кроме обновления самого токена, ваша функция должна ТАКЖЕ обновлять и refresh-token.
refreshToken() возвращает объект такого же формата, что и при авторизации по логину/паролю: с полями token, refresh_token и т. д.
Какой смысл хранить токены в sessionStorage? Почему не в самом клиентском коде? Раз уж он будет доступен только на время жизни клиентского приложения.
В статье указано, что можно хранить не только там, это зависит от наших нужд.
Проверка на клиенте как обычно лишь способ улучшить UX и снизить трафик и нагрузку на сервер.
Token, refresh token и создание асинхронной обертки для REST-запроса