Pull to refresh

Comments 45

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

Так этот вопрос нужно не автору задавать, а тем, кто придумал стандарт OAUTH.

Да и вообще сам OAUTH разрабатывался для распределнных систем, где у одной ресурсной системы доступа к аутентификацонной системы может и не быть. И если смотреть это как на замену кук, да, оно выглядит странно и переусложненно.

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

Токены это не альтернатива кукам, а ортогональный вопрос. С куками вы точно так же выбираете что в них будет, и это вполне могут быть токены. И тогда вся статья справедлива и для куков с чисто техническими поправками.

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

Добавлю что рефреш токен безопасно хранить как Куку с настройкой http only, что ограничивает возможность доступа к ней из js. А поскольку соединение https, то и возможность перехватить её обнуляется. Этот токен становится не доступен для перехвата.

Это чтобы злоумышленник мог отправить refresh-запрос и браузер сам подставил ему туда валидный токен?..

Да, чтобы вымышленный злоумышленник супер злодей мог обновить токен доступа (access). А классический автологин по куке разве не так работает?

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


Что вы понимаете под классическим автологином по куке — я не знаю, но обычно в классической схеме куки защищены запретом CORS. В "современной" же схеме, api разрешено использовать любому у кого есть токен доступа.


Собственно, вы же сами пишете ниже — "в статье рассказывается как сделать общедоступное апи, а не замкнутое на личный SPA". Такое API ни в коем случае не должно использовать куки.

Такое API ни в коем случае не должно использовать куки.

Это вряд ли. Я думал в моём апи нет кук, но оказалось что всякие сторонние штуки типа метрик, онлайн-чатов и пр. добавляют на сайт куки и они как минимум есть. Вообще куки относятся к клиенту (как я это понимаю), это его фишка, а не сервера (в конечном счёте это просто специальный заголовок в запросе). Чего точно НЕ ДОЛЖНО БЫТЬ так это СЕССИЙ.

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

куки защищены запретом CORS

В обще доступном апи наверное сложно задать конретный origin для запросов, но для самого рефрешь токена следует указать параметры path=/api/v1/auth/refresh & domain=api-site.ru

Не знаю как это зашитит от того о чём вы говорили изначально, но такая реализация вроде широкоизвестна в контексте Beaerer авторизации. Jwt это частный случай токена, который можно подставить в неё.

Не знаю как это защитит от того о чём вы говорили изначально

Никак. От CSRF можно защититься только анти-CSRF-токеном или политикой CORS. Для API работает только второй вариант (и то не всегда). Если нет возможности использовать CORS — остаётся только одно, не хранить ничего влияющего на безопасность в куках.

Зачем CORS? Зачем анти-CSRF-токен? Проставить SameSite=Strict и на этом вопрос CSRF по рефрешу в куках закрывается. Или нет?

Да, SameSite=Strict тоже будет работать, не знал про такое. Однако, это снова не вариант для защиты общедоступного API.

Вообще куки относятся к клиенту (как я это понимаю), это его фишка

Есть клиентские и есть серверные. Но в целом, сторона обычно их ставит для собственных нужд, а не противоположной. HttpOnly как пример серверных кук. Клиент с уровня приложения их как бы "не видит", но на уровне протокола - обязан возвращать при каждом запросе. Сервер может их потом использовать для идентификации клиентов, поддержки observability, авторизации и т.п.

Есть клиентские и есть серверные

Вы конечно правы, в том смысле, что бекэндер может установить Куку на клиенте. Но сервер не шлёт запросов клиенту (по протоколу http), поэтому он и не устанавливает куки. Клиент устанавливает куки, потому что именно он шлёт запросы.

Клиент с уровня приложения их как бы "не видит",

Их не видит только js в браузере, потому что так задумано. В другой реализации клиента можно сделать как захочется. И опять же, js не видит, а браузер видит и посылает их с запросом.

Пожалуйста, не используйте чистый JWT для пользовательский аутентификации. Токен хранится в Local storage и может быть похищен вообще всем, что может дотянуться до Local storage + привет CSRF атаки.

Передавайте JWT в cookie с флагами безопасности и префиксами типа __Secure-ваш_куки, если нужно. JWT никогда не были предназначены для пользовательский аутентификации и поэтому браузер их не защищает так как куки.

Вот как раз от CSRF токен, хранящийся в Local storage, защищён.

куки и жвт это не взаимоисключающие вещи. Куки это метод хранения и передачи данных, а жвт формат данных.

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

В рест апи не используются сессии, поэтому тут нет такой дилеммы и апи по настоящему без сохранения состояния. И JWT именно что для постоянной авторизации для апи без сохранения состояния.

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

О каком состоянии речь? Если данные аутентификации на бэке это состояние, то и БД это состояние?
Есть first-party аутентификация (как в вашем случае), с конкретными требованиями (возможность разлогина, таймауты, возможность посмотреть сколько у тебя сессий активных и т.д.). Есть third-party аутентификация (для этого придумали OAuth и JWT), с конкретными требованиями (делегирования доступа одних приложений к данным пользователей в других). JWT это инструмент, решающий другие задачи, противоположные вашей.
Я считаю, что ваша статья приносит больше вреда, чем пользы, потому что люди прочитают ее и будут дальше придумывать собственные алгоритмы аутентификации, что чревато. Секьюрити один из тех случаев, где лучше всегда взять готовое, если оно есть

О каком состоянии речь?

О том самом в каком протокол http - stateless.

Я считаю, что ваша статья

Вы ответили на мое сообщение, но статья не моя. Я считаю, что это не выдумка автора, а известная тема.

Да, конечно, извиняюсь. В любом случае обращался к автору

В рест апи не используются сессии

Это ограничение касается только уровня приложения: клиент-серверное приложение не должно хранить свое состояние на уровня транспорта. Иначе будут такие несуразности как зависимость результата от порядка запросов, невозможность кеширования и т.п.

Механизмы авторизации есть смысл оставлять за скобками. Т.е сначала авторизация, а внутри уже ReST - так чтобы приложение даже не подозревало. В таком случае одно другому не мешает. Для авторизации наоборот есть даже требования со стороны аудит логгинга и SIEM трекать пользовательские сессии, от момента активации и до конца.

Это ограничение касается только уровня приложения

Вообще, это определение REST API. Оно stateless.

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

Http протокол для stateless клиент серверного взаимодействия. Что значит хранить на транспортном уровне мне не хватает воображения что предоставить это.

Вообще, это определение REST API. Оно stateless.

API это абстракция. ReST ходит внутри Http, который передается внутри TLS/SSL, который внутри TCP, который по IP.

IP - stateless , TCP - statefull. TLS может по всякому, но чаще statefull ведь мы не хотим делать хендшейки при каждом запросе, а отзывать сертификаты наоборот хотим. Http - stateless. ReST - stateless.

Протокол авторизации на уровне приложения это не обязательно JWT. Здесь может быть Basic Auth, Kerberos, ntlm и куча всего другого. Он чаще определяется инфраструктурой, где развернуто приложение. Поэтому хорошей идеей будет не вносить протокол авторизации в API приложения вообще - это не его забота. В этом случае вы можете сохранить API stateless даже если авторизация statefull.

Вы таким образом, храня на сервере все, убили половину плюсов, оставив только «простоту». А нет, ее тоже убили.

# Проверяем валидность токена
        payload = decode(jwt=clear_token, key=JWT_SECRET, algorithms=['HS256', 'RS256'])

Код как бы говорит, что для того, чтобы декодировать токен, нужно передать туда ключ JWT_SECRET. Но это же не так, ключ нужен только чтобы проверить подпись токена.
Получается метод проверяет и декодирует токен, и лучше бы его назвать validateAndDecode. А глядя на это имя кажется, тут еще лучше было бы разделить метод на два.

Также мне не совсем понятно, как вы в реальности делаете авторизацию. У вас монолит или микросервисы? Если микросервисы, то они все получается знают ключ?
Вы не думали вынести логику генерации токена в отдельный сервис и перейти на схему с ассиметричным шифрованием?

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

Есть два типа JWT токенов: JWS, в котором данные только подписаны и JWE, где они ещё и зашифрованы. Возможно автор использует второй вариант.

Я так понял что JWT не декодируется.
Тут как с паролем да и вообще с хеш суммой - сравнивабются результаты преобразования, а не исходные значения.
Подписывается то что (header + payload) пришло и сравнивается с частью токена которая подпись, и если они совпадают, то все ок.
Все остальные утверждения (claims) типа срока действия, издателя и пр. берутся из первой части токена, которая Claims.

  • когда срок жизни access-токена уже истек или начинает подходить к концу, пользователь (или клиентское приложение) отправляет свой refresh-токен серверу, который его отзывает и возвращает новую пару.

Вот этот абзац не очень понятен (мне по крайней мере). Зачем отзывать refresh токен, если его время жизни не истекло? Или под новой парой подразумеватся этот же refresh-токен и новый access токен?

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

Т.е. refresh и access токены новые оба, но у нового refresh токена время жизни уменьшилось на соответствующее значение?

У рефрешь токена время жизни тоже самое. За счёт этого оно продлевается автоматически.

Почему заголовок про аутентификацию, а всё остальное - про авторизацию? Ведь перед тем, как предоставить доступ, пользователя нужно сначала идентифицировать, аутентифицировать, определить полномочия в системе и только потом организовать сессию. Для аутентификации средствами OAuth2 нужно реализовать ещё один поток (OIDC) с ID-токеном (он тоже JWT). Неплохое введение в картинках есть, например, на https://habr.com/ru/companies/flant/articles/475942/.

Про безопасность JWT можно почитать на https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html.

Аутентификация происходит до выдачи любых токенов. Сам ид-токен нужен для аутентификации на третьих системах. Если их нет то и токен такой не нужен. А авторизация происходит по аксесс токену.

Важно! Расшифровать токен может кто угодно

Ну это не совсем правда. Второй абзац того же jwt.io: Although JWTs can be encrypted to also provide secrecy between parties, we will focus on signed tokens...

Прочитав статью у меня появились вопросы к реализации.

Создав middleware вы заблокировали доступ к url с OpenApi по адресу «/docs», но это лишь часть беды. Как вы получаете доступ к регистрации пользователя, если перед созданием токенов для пользователя, в этой же middleware, идет проверка на наличие токена? Есть какая-то админская учетка, благодаря которой идет регистрация пользователя? Но тогда в чем идея, если каждый зарегистрированный пользователь может создавать других пользователей?

На самом деле, мы ничего не заблокировали. check_access_token, демонстрируемый в статье, выступает в роли зависимости, которую в рамках фреймворка FastAPI можно устанавливать на разные области видимости: конкретный обработчик, роутер или же всё приложение. У нас она установлена на роутер /users, поэтому, auth-обработчики токена не требуют.
Примерно такая же ситуация и с документацией (/docs), о которой вы пишете. Это изолированный путь, существовать которому мы никак не мешаем.
Всё это можно посмотреть и попробовать лично, склонировав наш репозиторий. Там всё контейнерезировано и с запуском приложения не должно возникнуть никаких проблем.

Отлично, что по моим вопросам уже есть решение, вероятно стоило описать это в статье, тогда некоторые вопросы сразу бы отпали

nbf (not before) — время, с которого токен должен считаться действительным;

Однажды столкнулся с ситуацией, когда 2 сервера бэкенда рассинхронизировались по времени на 5 секунд. Сервер, который выдает токен, и сервер, который читает. После чего все запросы с токеном стали падать.Правильный путь - поправить синхронизацию серверного времени. Костыль - библиотеки для работы JWT могут содержать переопределяемый параметр leeway, т.е. отсрочку от указанного в токене времени.

Спасибо за статью!

Насколько понял по коду в репозитории при проверке access_token идет обращение к базе для проверки не отозван ли токен и для поиска клиента по sub. При большом количестве клиентов не будет проблем с нагрузкой на БД? Кажется, одно из преимущест JWT как раз было в том чтобы избежать таких проверок.

Хотя первую проверку можно оптимизировать, если вынести в кеш (Redis...) список отозванных токенов.

Sign up to leave a comment.