Как стать автором
Обновить

Комментарии 40

Посетив страницу workers в Chrome ужаснулся количеству. Удалил все, отключил создание новых. Теперь 10% сайтов просто не грузится. "Спасибо" за такие технологии. Имхо, если worker не критически необходим, не стоит забивать браузер клиентам. Разве что очень хочется отслеживать что-нибудь.

Так и не понял как backend proxy что-то улучшает. Если злоумышленник может выполнить код на клиенте, то он с таким же успехом получит cookie и доступ через этот прокси. Он даже не заметит ничего.

И, как я вижу, никакого решения против XSS нет - если уж кто-то внедрился в код фронта, то ничто уже не спасёт.

НЛО прилетело и опубликовало эту надпись здесь

В статье рассматривается не защита от XSS, а защита от кражи сессии, или хотя бы минимизация ущерба, когда XSS уже произошел.

НЛО прилетело и опубликовало эту надпись здесь

Если злоумышленник может выполнить код на клиенте, то он с таким же успехом получит cookie

HttpOnly кука не доступна для чтения из JavaScript на клиенте - злоумышленник не сможет увести её наружу через XSS, чтобы воспользоваться где-то в другом месте.

Я говорю о том, что backend proxy не вносит никакой разницы в эту схему. Не важно, что на клиенте хранится не токен доступа, а некий "токен доступа к токену доступа", если по нему безо всякого ограничения можно получить реальный токен доступа. С таким же успехом его, настоящий токен доступа, можно хранить на клиенте в httponly cookie.

Покси нужен, чтобы положить токен (или его аналог) в куку - когда никто другой больше так не делает. Ведь бекенды, кидаясь друг в дружку запросами, обычно ожидают токен в заголовке Authorization: Bearer, - как настоятельно рекомендует делать OAuth2, - а не в куках.

Когда начинающему фронтендеру прилетает задача достать данные из API голого бекенда, то первое что приходит на ум - надо где-то сохранять Authorization токен, чтобы не ходить за ним на каждый запрос. Ну и в добавок менеджер давит: а можешь сделать сам, без бекендера, а то он и так перегружен? В ответ предлагаются чудесные варианты решения на голом JS в браузере: с local/session storage, или самому установить себе куку. Работы на 5 митут. Собственно так и начинается содомия, от которой предостерегает автор в своей статье.

Логично. Сначала запретим API ставить cookie, а затем напишем middleware, который будет их ставить.

Но смысл proxy теперь понятен...

Куки подразумевают хранение состояния между запросами. Это позволяет передавать меньше данных, но добавляет проблем в масштабировании. Клиенту мастабироваться и не за чем, а для бекендов это полезно. Поэтому там удобнее stateless протоколы.

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

А чем сервис воркеры не угодили?

Сильно улучшают UX при правильно использовании.

Честно говоря, пришёл к гибридному решению. аccess_token сохраняется в локалсторедже, но действует он максимум час. Его увод сильно погоды не сделает. А вот refresh_token - в http-only куке на стороне api-сервера, который выступает как прокси к oauth-серверу и именно он обменивает auth_code (или рефреш токен) на свежие токены. На api сервере же заодно хранятся и client id/client secret для конкретного ресурса, и обращение к этому конкретному эндпоинту и выдаёт в ответ новый акцесс токен веб-приложению.

У меня свой велосипед в сервисе аутентификации, но суть такая же, refresh-токен в httpOnly куке, с ограниченным path (только на продление токенов).
Хотелось бы услышать минусы.

Использование refresh-токена - пример известного компромисса между безопасностью и удобством пользователей, поэтому и стоит вопрос подбора его времени жизни.

Мне лично для их использования нравится совмещение ротации с защитой от переиспользования. Ротация refresh-токенов подразумевает, что при обращении с ним за получением access token мы получаем в ответе не только сам access token, а также и новый refresh token. При этом у нас получается некое "семейство" refresh-токенов, которые все идут от одного своего родителя.

И тогда, если у нас, скажем, злоумышленник похитил refresh token 2, а пользователь с ротацией уже получил следующий refresh token 3 мы можем сделать следующее: злоумышленник обращается с refresh token 2 за получением access token, мы видим, что происходит попытка переиспользования refresh token 2, который уже был использован, и инвалидируем все семейство этих токенов, поскольку мы не знаем, кто с каким к нам обращается, ведь это такие же Bearer-токены (если не используется другой подход).

Это интересный вариант, но в условиях нестабильного интернета может получиться ситуация, когда пользователь запросил обновление токенов, а из-за проблем со связью, ответ до пользователя не дошел (а на сервере токен уже пометили как использованный). И повторный запрос фактически разлогинит нашего пользователя.

Сложно найти баланс между удобством, отказоустойчивостью и безопасностью.

Нет ни малейшего резона как-то прятать токен на стороне клиента, если при этом клиентский код (а значит, и XSS) все так же способен запросить этот токен заново.

Если допустили XSS, то мы уже прилыли.

Если по какой-то причине мы хотим обезопаситься именно от использования этого токена злоумышлеником с другого компьютера, то можно привязать токен к айпи-адресу клиента или еще какому-то фингерпринту (это если мы этот токен сами контролируем/выдаем, конечно).

если при этом клиентский код (а значит, и XSS) все так же способен запросить этот токен заново

Здесь встает вопрос того, как у нас реализован запрос токенов и кто за ними обращается. Если это вынесено за рамки клиентской части, например, то такой трюк не пройдет.

можно привязать токен к айпи-адресу клиента или еще какому-то фингерпринту

Подход с привязкой сессий к IP-адресу был распространен в 2010-х годах, однако сейчас кажется применимым в более ограниченной области использования. Наша мобильность повысилась, мы можем при работе с веб-приложением переключиться с мобильного интернета на Wi-Fi, например, включить или отключить VPN. В таком случае при привязке к IP мы ощутимо жертвуем удобством пользователей.

Что значит "вынесено за рамки клиента"? Мне казалось, вся статья (кроме пункта с прокси, который чит сам по себе :) ) про то, что клиент в явном виде получает и использует токен. Если не получает и не использует, то и проблемы нет никакой.

Да, как раз говорю про бэкенд прокси или сервис воркер. Ну и смотря, про что мы говорим, имея в виду "запросить токен заново", если про использование refresh-токена, то я выше в другой цепочке написал пример мер для повышения безопасности его использования.

Ага.

Да, вариант с сервис воркером остроумный.

Не уверен, насколько часто необходимость защитить именно короткоживущий access token оправдывает возросшую сложность по поддержке сервис воркера. Но, наверное, иногда пригодится.

А не. Я еще нмного обдумал тему. Код в XSS мжет сделать то же самое, что делает сервис воркер при запросе refresh token. Получит новый рефреш токен, и по нему получит новый access token. Вывод: сервис воркер не нужен

Можно привязать access token к IP, и при смене сети будет происходить просто обновление его через refresh token. Прозрачно для клиента.

В то же время полностью защищаемся от кражи acess token-a (с другого IP он не будет работать сразу)

Подход с привязкой сессий к IP-адресу был распространен в 2010-х годах

Тут есть смысл использовать не только ip, а несколько параметров, но правда тут понадобится алгоритм как у платного fingerprint js, поэтому вряд ли кто-то будет с этим просто так заморачиваться

Есть бесплатные

Denizen.js например

Да несложно и самому написать хэш по IP, user-agent и еще чему-то

Всегда было интересно, чем пара Access Token/Refresh Token безопаснее http-only куки, устанавливаемой на короткое время и продливаемой бэком? Точно также не даст CSRF, и точно также при угоне в течении ее жизни можно будет эксплойтить бэк. В чем принципиальная разница?

Для access и refresh проще отследить событие кражи токенов. Если Боб обманом узнаёт у Алисы ее refresh токен и идёт обменивать его на новый access токен, то сервер должен инвалидировать токены Алисы. Тогда при следующем запросе Алиса получит ответ, что ее access токен не валиден и также попытается обменять старый refresh токен на новую пару access и refresh. Так как и refresh токен Алисы уже был использован Бобом, то сервер поймёт, что произошла кража токена и может инвалидировать все access и refresh токены, которые были получены в результате запросов с изначальным refresh токеном Алисы, то есть вылогинить и Алису и Боба, после чего Алиса сможет повторно пройти аутентификацию и получить новую валидную пару токенов, а Боб нет.

Я был уверен, что такое поведение является требованием стандарта, но как оказалось нет - OAuth2 даже не требует заменять refresh токен при перевыпуске access.

Я был уверен, что такое поведение является требованием стандарта, но как оказалось нет - OAuth2 даже не требует заменять refresh токен при перевыпуске access.

Да, я согласен, что было бы здорово это добавить в стандарт, чтобы повысить безопасность ванильных имплементаций. На текущий момент это отражено в драфте OAuth 2.0 Security Best Current Practice, который пока еще не стал RFC.

Есть клиент на нем access token, есть бэк, на нем refresh token, access token был уведен и в пределах его жизни Боб отлично работает с бэком, при его устаревании бэк продляет чей либо access token - Алисы или Боба, кто первый, после этого refresh token также перегенерируется и будет храниться на бэке - верно? Те узнать про компрометацию можно по невалидной паре at/rt и то, если она таки пройдет, а то если Алиса к бэку больше не обратится - то и детекта не будет.

В случае с кукой - при ее генерации она хранится и на клиенте и на сервере, при продлении она пересохраняется и при продлении также сравнивается что также даёт информацию о компрметации.

Или я что-то не так понял?

Не совсем. Угнанный access token дает Бобу возможность отправлять запросы на некий сторонний сервис (который выдал рефреш токен). Но по нему нельзя получить новый access token от нашего собственного сервера, который хранит refresh token. Для доступа к нашему бэку используем обычную сессионную куку.

Другой вариант - refresh token в Http-Only cookie. В принципе, то же самое, только в профиль.

Но по нему нельзя получить новый access token от нашего собственного сервера

Почему? Алисе ведь он продляет этот at, в чем проблема у Боба сделать тоже самое имея этот-же at?

Алиса его продляет, аутентифицируясь на нашем беке по собственной сессионной куке. Которую не угонишь.

Это если мы говорим про обращение к thrd-party сервисам, и при этом at хранится на клиенте в js.

А я говорил про авторизацию на своём собственном сервере с использованием ac/at. Получается что механизм этот ничем не лучше обычной сессии с кукой в httponly.

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

Ну дык. На своем (same site) сервере, конечно не нужны токены. Авторизуемся по HttpOnly куке. Необязательно короткоживущей (даже и вредно, пожалуй).

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

Токены и куки используются в разных сценариях. Токен для запросов к 3rd party доменам. Куки же first party, same site.

Что-то вы усложняете. Для авторизации на сайте используется кука с флагами httpOnly, secure, path и другими. В ее подписи участвуют IP и User Agent. Ну и базовая защита от CSRF.

На клиенте нужно как-то знать, залогинен пользователь или нет

Не совсем понял вопрос. Сделайте апишку `GET /api/me`, которая вернет словарик с полями текущего пользователя по куке.

И постоянно дёргать этот эндпойнт?

Когда нужно узнать, залогинен или нет, тогда и дергать.

Всегда интересовало почему популярно удтверждение "хранить токен в localstorage плохо, потому что если xss то ..."
Какая ведь разница в localstorage или нет? если УЖЕ есть xss, т.е. уже всё плохо.. как бы на уровень раньше. Уже и неважно где тот токен лежит, т.к. можно наделать что угодно с помощью xss (выдать себе новый токен, ждать пока залогиниться юзер и делать дальше плохие вещи и т.д.).

Токен может лежать в http-only cookie

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории