Зачем вообще нужны токены
При разработке приложений, общающихся с сервером, обычно вылезают следующие этапы:
- Приложение разрабатывает один разработчик, нет ни идентификации, ни аутентификации, ни авторизации (кстати, рекомендую замечательную хабрастатью на эту тему от DataArt), запросы напрямую ходят к эндпоинтам сервера, разработчик счастлив.
- Появляется второй разработчик, или тестировщик, или заказчик. Серверу становится важно знать, кто именно отправляет запрос. Добавляется шаг идентификации: простенькое окно «представьтесь» перед стартом приложения и крики «кто опять заходил под тремя единичками и все сломал?!?»
- Когда команда чуток устанет от «кто заходил под тремя единичками», на backend запилят форму регистрации с логином-паролем, которая будет проверять на уникальность логина. В таком виде продукт и будет разрабатываться до первой бета версии.
- В первой бета версии окажется, что сохранять пароль в plain text на устройстве — это как-то не очень безопасно. У сферического пользователя в вакууме один пароль от большинства сервисов, и очень не хочется быть крайним, через кого у пользователя взломали мейл, вконтактик и все остальное. Начинаются поиски решения «что бы такое сделать, чтобы пароль в явном виде не хранить». И через некоторое время команда приходит к тому или иному варианту «auth token».
Зачем нужен первый токен
Есть много разных токенов. Обычные, криптографические, «access key», «session token», разные схемы получения, использования и revoke. При этом одна из ключевых идей заключается в том, что если кто нехороший получит чужой токен, то самое неприятное, что случится — это доступ похитителя к сервису, от которого токен похищен. Пароль, тот самый, который один на все сервисы, похититель не получит. А пользователь, если поймет, что кроме него к сервису получил доступ кто-то другой, может токен отозвать. После чего получить себе новый, имея логин и пароль.
Зачем нужен второй токен
В OAuth 2 и некоторых других схемах авторизации (например, у нас) есть не один, а целых два токена. Первый, access token, используется при запросах к серверу (например, при логине). У него есть два свойства: он многоразовый и короткоживущий. Наш, к примеру, живет 48 часов, а у кого-то 30 минут. Время выбирается на основании того, как используется сервис. Второй, refresh token, используется для обновления пары access и refresh токенов. У него тоже есть два свойства, обратные первому токену: он одноразовый и долгоживущий. Совсем долгоживуший, наш живет месяц.
Схема использования у токенов следующая:
- Пользователь логинится в приложении, передавая логин и пароль на сервер. Они не сохраняются на устройстве, а сервер возвращает два токена и время их жизни
- Приложение сохраняет токены и использует access token для последующих запросов
- Когда время жизни access token подходит к концу (приложение может само проверять время жизни, или дождаться пока во время очередного использования сервер ответит «ой, всё»), приложение использует refresh token, чтобы обновить оба токена и продолжить использовать новый access token
Примерно так это все объяснено в документации OAuth, на Википедии, в нашей документации. И такое объяснение не отвечает на вопрос нафига?!? Зачем нужны два токена, если можно обойтись одним? В вопросе на stackoverflow даны несколько объяснений уровня «ну, access token можно менее надежно хранить чем refresh token и не бояться использовать вне HTTPS соединений. Например, хранить access token на frontend, а refresh token на backend» или «refresh token используется для доступа к заведомо безопасному сервису, а access token'ом потом можно тыкать во всякие подозрительные места и не очень бояться, что его сольют». Это может быть разумно для авторизации через Facebook и последующего использования без HTTPS. Но у нас-то везде HTTPS! И сервис у нас тоже один, наш. Зачем нам второй токен?
Зачем на самом деле нужен второй токен
Все оказалось и проще, и сложнее чем я думал. Следите за руками:
Случай 1: Боб узнал оба токена Алисы и не воспользовался refresh
В этом случае Боб получит доступ к сервису на время жизни access token. Как только оно истечет и приложение, которым пользуется Алиса, воспользуется refresh token, сервер вернет новую пару токенов, а те, что узнал Боб, превратятся в тыкву.
Случай 2: Боб узнал оба токена Алисы и воспользовался refresh
В этом случае оба токена Алисы превращаются в тыкву, приложение предлагает ей авторизоваться логином и паролем, сервер возвращает новую пару токенов, а те, что узнал Боб, снова превратятся в тыкву (тут есть нюанс с device id, может вернуть ту же пару что и у Боба. В таком случае следующее использование refresh токена превратит токены Боба в то, что изображено справа).
Таким образом, схема refresh + access токен ограничивает время, на которое атакующий может получить доступ к сервису. По сравнению с одним токеном, которым злоумышленник может пользоваться неделями и никто об этом не узнает.
Комментарии (277)
lega
06.03.2017 11:41+1приложение предлагает ей авторизоваться логином и паролем, сервер возвращает новую пару токенов, а те, что узнал Боб, снова превратятся в тыкву
С такой схемой будет невозможно быть залогиненным на двух устройствах? Дом + работа, или ноутбук + мобильный, каждый раз нужно будет вводить пароль? Это не удобно.napa3um
06.03.2017 11:50Возможно быть залогиненным, неразделяемыми между сессиями является только токены, а не логины-пароли.
jackes
06.03.2017 11:53Если я правильно понял, ничто не мешает нам сгенерировать 2 пары токенов: одну для работы, другую для дома
Evengard
06.03.2017 11:55+1Суть в том, что при использовании рефреш-токена инвалидируется ТОЛЬКО тот, который использовался. Если у вас два рефреш токена, и вы рефрешнули только первый, второй-то никуда не делся, и остался валидным.
lega
06.03.2017 12:18использовании рефреш-токена инвалидируется ТОЛЬКО тот, который использовался
В таком случае для второго примера, производный токен Боба будет продолжать работать. Надо инвалидировать все производные ключи (хранить деревья). И инвалидный refresh токен можно обноаить с паролем, иначе Алиса не сможет себе его обновить.lega
06.03.2017 12:25Тогда Бобу надо испортить/стереть токены Алисы при копировании, чтобы она сгенерировала себе новые независимые токены. Возможно она не будет деактивировать все устройства, только из-за того что с неё очередной раз запросили пароль. В результате и Боб и Алиса будут иметь рабочие, обновляемые токены.
Razaz
06.03.2017 12:35-2Основной смысл Refresh token — оффлайновый доступ — даже называется offline_access. Access of a protected API as proof of authentication: Refresh tokens and assertions can be used to get access tokens without the user being present, and in some cases access grants can occur without the user having to authenticate at all.
Что бы креденшалы не вводить — на AS/Idp тупо используется SSO сессия и при протухании access_token и валидной сессии ничего вводить не надо.
Кейс Refresh token — вы дали авторизовали сервис, который автоматом делает вам красивые галереи из ваших фоток в фэйсбуке. Выгружать их он может и без вас, сидящего за компом, поэтому он попросит скоуп offline_access и будет получать токен для вызова апи когда ему надо.eyeofhell
06.03.2017 13:04Кейс Refresh token — вы дали авторизовали сервис, который автоматом делает вам красивые галереи из ваших фоток в фэйсбуке. Выгружать их он может и без вас, сидящего за компом, поэтому он попросит скоуп offline_access и будет получать токен для вызова апи когда ему надо.
Что мешает этот кейс сделать с одним токеном?Razaz
06.03.2017 13:14Каким?
eyeofhell
06.03.2017 13:47-1Просто токен. GUID. В иллюстративных целях назовем его «razaz token» и будем выдавать после логина.
Razaz
06.03.2017 14:00И что вы с ним будете делать? :) Это как я понимаю ссылка на нормальный токен, который храниться на AS/IdP?
Refrsh token — это креденшалы. По которым приложение от имени пользователя, который дал разрешение на этом может получить доступ в API.
eyeofhell
06.03.2017 14:28«Выгружать их он может и без вас, сидящего за компом, поэтому он попросит скоуп offline_access и будет получать токен для вызова апи когда ему надо.» < — а вот это и буду делать. Фоточки выгружать. Чем один токен при таком использовании хуже?
Razaz
06.03.2017 14:35Какой один? access_token короткоживущий. А refresh на недели выдаваться может.
Вы уточните что вы подразумеваете под токеном и какие метаданные о нем вы храните.eyeofhell
06.03.2017 14:43Просто токен. GUID. В иллюстративных целях назовем его «razaz token» и будем выдавать после логина, хранить на стороне Authority что сочтем нужным. Чем такой токен плох для offline, о котором вы писали?
Razaz
06.03.2017 14:511. Вы описали обычный Refresh. Получаете референс/handle(только не гуид, а сгенерированный нормальным криптографическим RNG). Используете его для получения access_token и все.
2. Это не стандарт со всеми вытекающими.eyeofhell
06.03.2017 15:05«Основной смысл Refresh token — оффлайновый доступ» слабо коррелирует с «Это не стандарт». Либо основной смысл в том что стандарт, либо что оффлайн. Если оффлайн — то непонятно, зачем для этого два токена, когда можно обойтись одним.
Razaz
06.03.2017 15:09Это стандартный механизм получения доступа к защищенному API при отсутствии активной сессии пользователя(пользователь offline).
1. Есть такая штука как audience. Дак вот у refresh_token audience — это ваш AS/IdP, а у access_token — список ресурсов, к которым он применим. Если используются JWT, то в него все зашивается для access_token + скоупы.
2. refresh_token — Это креденшалы для аутентификации на AS/IdP. access_token — несет в себе(или ссылается) информацию для авторизации в API.
eyeofhell
06.03.2017 15:13Это вы так прозрачно намекаете, что идея двух токенов в том, чтобы сервер аппы мог использовать refresh_token без авторизации, потому что ему вроде как «доверяют»?
Razaz
06.03.2017 15:26Еще раз:
При refresh_topken гранте аутентифицируется клиент при помощи своего способа(basic, mutal tls, client assertions) + предоставляет креденшалы пользователя в виде refresh_token, а не логин/пароль.
Это все происходит на AS/IdP, а не на сервере с API.
centur
06.03.2017 13:36https://tools.ietf.org/html/rfc6749#page-10
Спека с вами не согласна. скоупы там просто для того чтобы определить что можно с токеном звать а что нет, offline_access — это вообще чей то частный скоуп, в спеке ни слова про offline.
Вы случайно не путаете чью-то реализацию Oauth 2.0 (Facebook? ) с самим протоколом?
lair
06.03.2017 13:25+2Кстати, я вот тут подумал что-то. А у вас клиент неаутентифицированный?
eyeofhell
06.03.2017 13:50Он же парой логин-пароль вначале представляется. Логин его идентифицирует, пароль — аутентифицирует. Сервер потом авторизирует :) Все по фен-шую.
lair
06.03.2017 13:57+1Логин-пароль — это вы, наверное, про пользователя говорите. А я про клиент (в смысле, программу) в терминах OAuth.
eyeofhell
06.03.2017 14:31Мы используем внутри сдк еще токен устройства, который сами генерим.
lair
06.03.2017 14:33Он валидируется сервером, или кто угодно может прислать любой?
eyeofhell
06.03.2017 14:44Валидируется логин+пароль или access token. Device ID позволяет одному пользователю залогиниться с нескольких устройств, зачем его валидировать? Всегда же можно разобрать клиентское SDK и посмотреть, как оно считается.
lair
06.03.2017 14:50+1Ну то есть клиент у вас неаутентифицированный. Просто одна из степеней защиты при рефреше токена — это проверка client credentials. Вам она, в силу того, что у вас public client, она недоступна.
Razaz
06.03.2017 14:54То есть у вас комбинация из public client + refresh_token + access_token лайфтайм 2 дня?
eyeofhell
06.03.2017 15:09Пока да. Если будут реалистичные кейсы почему это плохо — подкрутим. Да, вообщем-то, если придет клиент и скажет «хочу 10 минут» — ничто не помешает для его акка подкрутить. You are welcome.
Razaz
06.03.2017 15:231. refresh_token на публичном клиенте(то есть клиент не аутентифицируется сам) это фактически дырень, если у вас нет ротации токенов или какой-то хитрой эвристики, что бы 100% убедиться что это именно тот клиент которому вы его выдали.
2. access_token — короткоживущий. Час уже достаточно большое время жизни.
eyeofhell
06.03.2017 15:25refresh_token на публичном клиенте(то есть клиент не аутентифицируется сам) это фактически дырень, если у вас нет ротации токенов или какой-то хитрой эвристики, что бы 100% убедиться что это именно тот клиент которому вы его выдали.
Сценарий атаки в студию!Razaz
06.03.2017 15:27XSS.
eyeofhell
06.03.2017 16:12XSS для Android SDK? Как?
Razaz
06.03.2017 18:05+1Если это не браузер и клиент может безопасно хранить токены, то это вопрос доверия к хранилищу, где содержится токен.
Для такого кейса необходимо как минимум однозначно идентифицировать клиента(конкретное устройство).
Тут уже ваше дело — доверяете ли вы Android в хранении таких критических вещей или нет.
GreenStore
06.03.2017 15:20+4> Случай 1: Боб узнал оба токена Алисы и не воспользовался refresh
> Случай 2: Боб узнал оба токена Алисы и воспользовался refresh
Вопрос: а в случае только одного токена разве не тоже самое можно сделать?
Копирую почти один-в-один, только с одним токеном:
Случай 1: Боб узнал токен Алисы и не воспользовался refresh
В этом случае Боб получит доступ к сервису на время жизни token. Как только оно истечет и приложение, которым пользуется Алиса, воспользуется token, сервер вернет новый токен, а тот, что узнал Боб, превратится в тыкву.
Случай 2: Боб узнал токен Алисы и воспользовался refresh
В этом случае токен Алисы превращается в тыкву, приложение предлагает ей авторизоваться логином и паролем, сервер возвращает новый токен, а тот, что узнал Боб, снова превратится в тыкву (тут есть нюанс с device id, может вернуть тот же что и у Боба. В таком случае следующее использование токена превратит токен Боба в то, что изображено справа).
Ну и зачем тогда пара токенов?eyeofhell
06.03.2017 15:24Хороший вопрос. А какое время жизни будет у этого токена? Предположим, 30 минут, как обычно у access (у нас 48 часов потому что use case специфичный). Алиса залогинилась, посмотрела фотки, выключила телефон и села в метро. Через час вышла из метро, включила телефон, запустила аппу… и аппа ее просит пароль, потому что токен протух. Не круто :)
VasiliySS
06.03.2017 17:54+1Позволять по access делать запросы в течение получаса, а регенерировать новый токен — в течение двух суток (ну или сколько там рефреш живет). По всем кейсам практически то же самое, но токен один.
eyeofhell
06.03.2017 17:55Single Responsibility Principle :)
frol
07.03.2017 19:13Вот это единственное верное оправдание для вашего юзкейса с одним сервером. Всё что приведено в статье можно реализовать на одном токене, как верно предложил VasiliySS, и никакой защищённости в этом нет. Ну, разве что refresh token реже отправляется, но если access token живёт 30 минут, то уже через 30 минут злумышленник достигнет своей цели. OAuth2 НЕЛЬЗЯ использовать в незащищённых подключениях, и это ключевое изменение, которое позволило значительно упростить OAuth1.
Из хороших объяснений зачем нужно два ключа я для себя выделил одно:
Использование двух токенов позволяет проверять access token без необходимости хранить его в БД, то есть можно существенно уменьшить нагрузку на БД (избавляемся от одного SQL запроса на каждом HTTP запросе). Как сделать проверку access токена без БД? Элементарно — сервер авторизации должен криптографически подписывать ID пользователя + срок годности токена + случайный текст (то есть access token будет выглядеть как <user_id><expiration_date>), тогда API серверу достаточно проверить цифровую подпись в access token и сверить время жизни, и если всё ОК, то можно считать, что пришёл запрос от пользователя user_id. Очевидный недостаток такого подхода — нельзя отозвать access token, поэтому делают короткоживущий access token и отзывают только refresh token.
lair
06.03.2017 18:12Если по access-токену можно сделать рефреш, то, перехватив access, можно дальше устроить себе сколько угодно рефрешей. Теряете в безопасности.
Rothmansua
06.03.2017 20:53Чтобы реже спрашивать активного пользователя его пароль (см. комментарии ниже)
Veha
06.03.2017 17:30А что если access_token не хранить на стороне приложения, а использовать только для одной сесии и с коротким сроком жизни? А хранить refresh_token и каждый раз при инициализации приложения получать новый access_token и refresh_token. Таким образом если ктото украл токены и использовал refresh_token то при запросе пользователя после инициализации или попытке обновить токены, refresh_token не совпадет с тем что в базе и пользователя попросят ввести логин пароль, что приведет к обновлению refresh_token в базе и украденым уже нельзя будет воспользоватся
lair
06.03.2017 17:38А что если access_token не хранить на стороне приложения, а использовать только для одной сесии и с коротким сроком жизни?
Тогда вам придется слать рефреш перед каждым запросом, что делает это эквивалентом аксесса.
Таким образом если ктото украл токены и использовал refresh_token то при запросе пользователя после инициализации или попытке обновить токены, refresh_token не совпадет с тем что в базе
А почему не совпадет?
Razaz
06.03.2017 18:141. Зачем? Как раз все правильно. Запрос на рефреш отправлять если токена нет или истекает.
2. Если сделана инвалидация старых рефреш токенов то будет такое поведение.
Тут вопрос в том, что если канал скомпрометирован, то поздно пить Боржоми ;)lair
06.03.2017 18:18Зачем? Как раз все правильно. Запрос на рефреш отправлять если токена нет или истекает.
Потому что если "не хранить" access-токен, то у нас всегда сценарий "токена нет".
Если сделана инвалидация старых рефреш токенов то будет такое поведение.
Ключевое слово "если". Она не обязательна.
Razaz
06.03.2017 18:20Я имею ввиду не хранить перзистентно :) В памяти и при закрытиии приложения удалять.
Согласен. Но лучше ее по умолчанию включить если есть возможность :)lair
06.03.2017 18:23Я имею ввиду не хранить перзистентно :) В памяти и при закрытиии приложения удалять.
Тогда нет никакого отличия от просто короткого срока жизни. Особенно учитывая, насколько призрачны понятия "время жизни" и "закрытие приложения" на мобилке.
Razaz
06.03.2017 18:30Не спорю, что access_token должен быть короткоживущим. Чем меньше мобилка знает — тем лучше.
Rothmansua
06.03.2017 18:17Refresh Token нужен для того, чтобы можно было задавать разные таймауты для «активной» сессии (когда пользователь непрерывно работает) и для «неактивной» сессии (когда ушел не закрыв браузер).
Т.е. повышение удобства использования (реже спрашивать пароль) без значительного понижения уровня безопасности.Razaz
06.03.2017 18:19Если клиент — браузер, то достаточно SSO + access_token. Рефреш тут не нужен. Время основной сессии контролируется на IdP, а сессия клиента короткоживущая и периодически рефрешится у IdP простым редиректом на authorize эндпоинт.
Rothmansua
06.03.2017 18:25Речь идет о такого рода настройке:
«если пользователь непрерывно работает со страницей, то сессия истекает через 8 часов рабочего дня»
«если пользователь не делал никаких операций, сессия закрывается через 15 минут ожидания»
Решается это разным таймаутом валидности для access и refresh token'a.
«периодически рефрешится у IdP простым редиректом на authorize эндпоинт»
вот здесь поподробнее пожалуйста, снова пользователю пароль вводить?lair
06.03.2017 18:26Речь идет о такого рода настройке:
«если пользователь непрерывно работает со страницей, но сессия истекает через 8 часов рабочего для»
«если пользователь не делал никаких операций, сессия закрывается через 15 минут ожидания»… и как вы это сделаете через refresh + access?
Rothmansua
06.03.2017 18:321) Access token валиден 15 минут
2) Через 15 минут клиент получает «отлуп» при попытке выполнить очередную операцию.
3) Вместо того чтобы снова спрашивать пользователя ввести пароль, клиент отправляет refresh token для аутентификации.
4) Refresh token валиден 8 часов
5) Повторять шаги 2-4 8 часов подряд
6) Через 8 часов попытка аутентификации с помощью refresh токена закончится неудачей и тогда уже можно спрашивать пароль по-новойlair
06.03.2017 19:04Эмм.
- У вас через восемь часов "сессия закрывается" — нужен пароль, а через 15 минут "сессия закрывается" — пароль не нужен. Немножко не одно и то же.
- Чем отличается "клиент ничего не делал 15 минут и получил отлуп по истечению access-токена" и "клиент что-то делал 15 минут и получил отлуп по истечению access-токена"?
Rothmansua
06.03.2017 19:13Смысл refresh token'a как раз в том, что с ним для получения access token'a не нужен пароль.
Если есть валидный refresh token, то пользователь в UI ничего не заметит когда его access token истечет и просто незаметно продолжит работу дальше, и так 8 часов.
Если бы у пользователя/клиента не было бы валидного refresh token'a, то единственным способом получить/обновить access token для продолжения работы была бы аутентификация «с самого начала», т.е. предъявления своего секрета (пароля). В этом случае, очевидно, пришлось бы вводить пароль каждые 15 минут.
Если «украли/утек» refresh токен — то конечно беда на следующие 8 часов, лучше его бережно хранить на клиенте. Но зато он хоть путешествует по сети редко (раз в 15 минут, а не при каждом запросе сервера), так что перехватить его труднее (а по SSL и того сложнее).
https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/lair
06.03.2017 22:34Это все хорошо, но ваше же требование "если пользователь не делал никаких операций, сессия закрывается через 15 минут ожидания" не выполнено:
- с точки зрения пользователя нет разницы, работал он 15 минут, или ничего не делал — все равно UI незаметно воспользуется рефрешем для получения нового токена
- с точки зрения системы тоже нет разницы, работал пользователь 15 минут или ничего не делал — все равно через 15 минут один токен будет заменен на другой.
При этом, что характерно, ваша пара требований прекрасно решается одним токеном со следующей комбинацией правил: скользящее время устаревания с окном в 15 минут + абсолютный максимум устаревания в 8 часов. Пользователь не работал 15 минут — все, токен протух, вперед вводить логин-пароль. Пользователь работал каждые десять минут — через восемь часов все равно вводи логин-пароль (но все эти восемь часов будет один и тот же токен, одна и та же отслеживаемая сессия).
Rothmansua
07.03.2017 01:212. С точки зрения системы, если пользователь закрыл браузер и ушел никто не сможет воспользоваться перехваченным access token'om через 15 минут. Refresh token при этом перехватить сложнее, потому что существенно реже передается по сети.
Теперь неужели не ясна разница в уровне безопасности между двумя следующими сценариями:
1. Один и тот же access token гуляет по сети при каждом серверном запросе 8 часов (хоть следите вы на сервере за разной длинной активной/неактивной сессии, хоть не следите)
2. Access token меняется каждые 15 минут атоматически.
По вашему это равнозначно и можно обойтись только access токеном?lair
07.03.2017 10:12С точки зрения системы, если пользователь закрыл браузер и ушел никто не сможет воспользоваться перехваченным access token'om через 15 минут.
Если пользователь не закрывал браузера, все равно никто не сможет воспользоваться перехваченным токеном через 15 минут. Поэтому требование продолжает быть не выполнено.
Теперь неужели не ясна разница в уровне безопасности между двумя следующими сценариями:
Разница в уровне безопасности была понятна с самого начала. Речь идет именно о требованиях на время жизни сессии.
jetexe
07.03.2017 10:13Потому что вы забываете, что ещё есть «сессия», поверх неё защита от CSRF. Куда ещё больше токенов?
Rothmansua
07.03.2017 10:42Почему это я забываю о «сессии»?
Вы видимо имеете в виду «HTTP сессию» на Resource Server'e. На мой взгляд, эта штука достаточно ортогональная «OAuth2 сессии», и, к тому же, которую следует избегать, чтобы не слишком усложнять дизайн системы (иначе приходится городить репликацию сессии или sticky session на load balancer'ах).
Rothmansua
07.03.2017 11:04Пользовательскую сессию не обязательно реализовывать в виде HTTP сессии.
Если делать сервисы stateless и не хранить ничего пользовательского в памяти, вполне можно обойтись OAuth2 токенами для определения когда сессия, определенная ими, истекла: как только authorization server отказывается аутентифицировать access и/или refresh token переправляем пользователя на Login screen.jetexe
07.03.2017 11:28Если клиент в браузере, то HTTP сессия у него уже есть. И для получения access можно использовать её. OAuth2 немного не для этого всё же.
Razaz
06.03.2017 18:261. Максимальное время продления SSO сессии.
2. Базовое время жизни SSO сессии.
Sliding expiration называется такая штука.
Зачем? У юзера есть SSO сессия. Пока она активна IdP не будет предпринимать попыток заново аутентифицировать пользователя.Rothmansua
06.03.2017 18:29OAuth2 и SSO — две разные и независимые вещи
Razaz
06.03.2017 18:31OAuth2 обычно идет в довесок к OpenID Connect у основных провайдеров. А там и SSO обычно вкручен.
Rothmansua
06.03.2017 18:34«обычно» :)
Повторюсь, OAuth2/OpenID Connect и SSO (который на чем душа пожелает можно реализовывать, хоть SAML2, хоть JWT) — две совершенно разные вещиRazaz
06.03.2017 18:38JWT не SSO :)
Можете заменить SSO на сессию IdP.Rothmansua
06.03.2017 18:411) Где я сказал, что JWT это SSO?
2) Так я о том же, SSO тут ни при чем, уберите его из своих комментариев и мы с вами быстро согласимся :)Razaz
06.03.2017 18:45Сойдемся на том, что хватит перзистентной сессии на IdP и не надо заново ничего вводить :)
Но SSO уже в каждом нормальном IdP есть ;)
Rothmansua
06.03.2017 18:30Расшифруйте IdP пожалуйста «ID Provider»?
Razaz
06.03.2017 18:32Identity Provider. Может выступать в роли Authorization Server.
Rothmansua
06.03.2017 18:35Тот который следит за временем валидности токенов (сессии) в моем случае.
Razaz
06.03.2017 18:39За временем валидности может следить как клиент так и IdP.
Клиент может смотреть клэймы если это JWT токен напрмиер.Rothmansua
06.03.2017 18:44Вы мешаете кучу терминов в одну кучу… Очень долго раскручивать.
JWT — это формат носителя SSO token'a
К OAuth2 и refresh token'у это [прямого] отношения не имеет.
Повторяю еще раз, мое утверждение валидно для OAuth2.
Я не утверждал и не собираюсь доказывать, что оно валидно для SSO.Razaz
06.03.2017 18:48-1JWT это формат передачи Assertions. Для SSO он не сдался. Информацию о сессии IdP может просто хранить в куке.
Начинает иметь, когда access_token представлен в виде JWT и содержит exp клэйм.
Думаю надо просто взять чуток шире и добавить OIDC ;)
Rothmansua
06.03.2017 18:28И SSO тут вообще ни при чем.
Razaz
06.03.2017 18:32Описанная вами проблема решается не через refresh_token.
Rothmansua
06.03.2017 18:38Refresh Token — часть OAuth2 протокола.
Моя проблема не имеет ничего общего с SSO.
В ней речь идет об аутентификации в пределах одной системы, а не Single Sign On в несколько разных независимых.Razaz
06.03.2017 18:40И основной кейс — это получение доступа к ресурсу, когда нет пользовательской сессии.
Rothmansua
06.03.2017 18:46Я скорее не соглашусь.
Но смотря как вы определяете пользовательскую сессию.
Если пользовательськая сессия — это время валидности access token'а, то да.
Иначе — нет.Razaz
06.03.2017 18:51access_token — выдается обычно для обращения к ресурсу. То есть вам делегируется временный доступ куда-то. Использовать его для своей сессии некорректно. Как раз тут надо включать еще OIDC.
Rothmansua
06.03.2017 19:01Извините, но это (e.g. «использовать его для своей сессии») все больше напоминает какую-то демагогию.
Предлагаю разобраться самостоятельно в спокойной обстановке.Razaz
06.03.2017 19:18Вы можете не иметь доступа к времени жизни этого токена. Можете использовать факт его получения, как основание для аутентификации, но время жизни сессии на клиенте тут не будет кореллировать никоем образом. Можем в личке пообщаться, но думаю дискуссия будет хорошим дополнением к статье :)
Veha
06.03.2017 19:31Разница между access_token и refresh_token:
— access_token не нужно хранить в базе данных, с помощью JWT можно хранить данные в токене — например userId, таким образом при каждом запросе к серверу мы избавляемся от лишнего запроса к базе данных так как ID юзера можно получить из токена
— refresh_token хеш который хранится в базе данных.
1. Сервер получает логин/пароль — создает access_token и refresh_token, сохраняет в базу refresh_token, отдает токены на клиент
2. Клиент использует access_token для доступа к данным пока приложение не будет закрыто, сохраняет refresh_token в хранилище
3. Если получает от сервера сообщение что срок access_token истек — отправляет запрос на авторизацию с refresh_token, если refresh_token не истек и совпадает с тем что в базе — сервер создает access_token и refresh_token, обновляет в базе refresh_token и отдает на клиент. Если refresh_token не валиден просит ввести логин/пароль
4. При инициализации приложения если есть refresh_token — делает тоже что и в 3
OlegMax
06.03.2017 19:58-1Наконец-то первый правильный ответ. Да, два токена сделаны для удобства сервера.
«Implementations MUST support the revocation of refresh tokens and SHOULD support the revocation of access tokens». Это позволяет пользоваться access token'ом без обращения к базе.
The access tokens may be self-contained so that a resource server needs no further interaction with an authorization server issuing these tokens to perform an authorization decision of the client requesting access to a protected resource.Rothmansua
06.03.2017 20:46Тут какое-то явное противоречие.
С одной стороны мы поддерживаем revocation access token'a, а с другой не обращаемся к authorization server'у и БД чтобы проверить, а не «отозван» ли наш токен :-/OlegMax
06.03.2017 21:45Именно, что мы можем не поддерживать revocation access token'a. SHOULD имеет значение «хорошо бы, но если нет, то и так сойдет».
Rothmansua
06.03.2017 22:06Ну да, на логаут тоже можно забить. :) (что такое revocation по-вашему?)
И вообще, зачем с этой аутентификацией возиться ;)
lair
06.03.2017 22:36+1access_token не нужно хранить в базе данных,
Только в том случае, если он self-contained. Иногда — в том числе и по соображениям безопасности — access-токен все равно используется ссылочный. Это не противоречит никакому стандарту.
с помощью JWT можно хранить данные в токене — например userId, таким образом при каждом запросе к серверу мы избавляемся от лишнего запроса к базе данных так как ID юзера можно получить из токена
Не надо так делать. Используйте OIDC и identity token.
Rothmansua
06.03.2017 20:42А как проверять правильный ли переданный access token, если не хранить его в БД Authorization Server'a?
Можно конечно держать их в памяти и сравнивать, но тогда ваш Authorization Server перестает быть stateless, скалируется плохо, начинает требовать репликации данных в памяти между инстансами, возможно sticky-sessions на load balancer'e и все это только потому что мы решили не хранить его в БД.Razaz
06.03.2017 20:53Rothmansua
06.03.2017 20:56И?
Я это и имею в виду.Razaz
06.03.2017 21:08Хранение токенов в принципе обосновано. Мы(IdP) храним все выданные токены до момента их протухания/использования. Тут зависит от настроек клиента — хочет он получать полный токен или сслыку, будет он использовать интроспекцию или нет, будет он пробрасывать токен дальше и тд.
Масштабируется вполне прилично — пара инстансов на 200к+ юзеров.Rothmansua
06.03.2017 21:24И что, все токены в памяти «вашего IdP» (а не БД) всегда хранятся?
Собственно это даже круто (быстрее ответ), только решение существенно сложнее (вы же не сами «ваш IdP» писали?)
Veha
06.03.2017 21:02А как проверять правильный ли переданный access token
Вы читали как работает JWT? access token не нужно держать на стороне сервера. Пример использования на сервере:
— создаем токен:
var token = jwt.sign({
userID: '123'
}, 'secret', { expiresIn: '1h' });
— проверяем:
var decoded = jwt.verify(token, 'secret');
// decoded.userID = 123Rothmansua
06.03.2017 21:07Как я уже писал выше, JWT — это формат. К OAuth2 протоколу прямого отношения не имеет. Используется чаще для установления SSO между независимыми системами (из-за своей способности нести в себе дополнительную информацию).
Теперь возвращаемся «к нашим баранам».
Итак, как верификация в вашем примере (того что JWT токен не поддельный) поможет без обращения к системе, которая его выдала, проверить отозван токен или нет?
lair
06.03.2017 22:37А
jwt.verify
святым духом работает? Валидность подписи вы как проверяете?Rothmansua
07.03.2017 00:53При чем здесь это?!
jwt.verify проверяет только что
1) токен подписан и выдан исходным authorization server
2) (опционально) он зашифрован с использованием секрета, известного вам
Если вы потеряли access токен, люой может его использовать для OAuth2 аутентифкации пока он валиден.
Теперь при взломе или досрочном прекращении работы (logout) вы должны ивалидировать access token (чтобы кто-нибудь не смог его использовать после конца вашей работы и до конца срока его действия.
При чем здесь jwt.verify() ?!Rothmansua
07.03.2017 01:26А и да, jwt.verify() не подразуменвает раскодирования, так что пункт 2) зачеркните там (шифрование SSO token'ов практикуется в определенных схемах SSO).
Перехваченный валидный (до тех пор пока не было логаута) OAuth2 access token может использовать любой злоумышленник для аутентификации.
Нет логаута — все ваши JWT access token'ы (что вы прицепились к этому JWT, очень много систем используют простой GUID в качестве access token'a) остаются валидными в руках злоумышленника до истечения срока их годности.
lair
07.03.2017 10:03Ровно при том, что даже если мы отказались от token revocation, использование JWT все равно не позволит обойтись без обращений к серверу при проверке токена — потому что нельзя "просто взять и проверить", что токен выдан именно тем, кем утверждается, что он выдан.
(мне кажется, вы перепутали, кому я отвечал)
Rothmansua
07.03.2017 10:49Сорри, если я что перепутал.
Но вынужден снова не согласиться. Смысл подписи и jwt.verify() именно в том, чтобы удостовериться в том, кто выдал токен, без обращения к нему.
Это свойство часто используется в схемах SSO систем без центрального/общего identy provider.
Т.е., грубо говоря, если мы отказываемся от revocation и логаута, то, похоже, можно обойтись без обращения с authorization server'у.lair
07.03.2017 10:50Смысл подписи и jwt.verify() именно в том, чтобы удостовериться в том, кто выдал токен, без обращения к нему.
Угу, и как же именно?
Rothmansua
07.03.2017 10:57Ну как, святым духом вестимо :)
https://jwt.io/introduction/
Вкратце, с помощью ассиметричных алгоритмов шифрования.lair
07.03.2017 11:10Ну мы же взрослые люди, мы знаем, что в ассиметрике надо валидировать сертификат.
GreenStore
07.03.2017 11:18Токены же проверяются на сервере, поэтому организация PKI или дистрибуция Shared Key не является проблемой. Или я не понял суть вопроса?
Rothmansua
07.03.2017 11:22Ну обычно да.
Resource Server обращается при каждом клиентском запросе к authorization server, чтобы убедиться, что все ок.
Но
Если вдруг authorization server недоступен (как например в сценарии SSO между двумя независимыми системами), то обращаться за проверкой некуда (например нету сетевой связи с AS, выдавшим токен).
Тут его подлинность можно проверить только с помощью криптографической подписи токена.Razaz
07.03.2017 11:26С чего вы взяли? Онлайн проверка используется для референс токенов. Для self-contained в большинстве ситуаций смысла нет. Ну если вы не ставите лайфтайм неадекватно большим.
GreenStore
07.03.2017 11:32> Resource Server обращается при каждом клиентском запросе к authorization server, чтобы убедиться, что все ок.
А зачем ему это делать каждый раз, если он кэширует актуальный сертификат CA или имеет сконфигурированный/периодически обновляемый Shared Key?
И если это необходимо, может периодически обновлять список отозванных токенов.
Кроме этого, необходимые данные authorization server легко реплицируются, т.е. система без труда горизонтально масштабируется.Razaz
07.03.2017 11:38Токены могут быть self-contained(содержать все в себе) или reference(ссылка на токен, который хранится на AS).
Rothmansua
07.03.2017 11:20Почему это сертификат?
Ассиметричное шифрование предполагает использование пары public и private ключей.
Подпиши токен private ключем и раздай private key всем пользователям, кто об этом попросит, свой public key и вот они уже могут счастливо проверять подлинность JWT токенов без лишних раундтрипов к тебе же.
(я такое делал, и именно с JWT токенами, все работает)Razaz
07.03.2017 11:23CRL, Chain trust, Expiry. Раздают публичную часть.
Rothmansua
07.03.2017 11:26Да можно просто в Яваскрипте public key хардкодить.
Если доверяешь домену, с которого скачиваешь его, то этого должно быть досточно.Razaz
07.03.2017 11:29Какой паблик кей в JS??? Не достаточно. Сертификат для подписи не должен совпадать с сертификатом для TLS
Rothmansua
07.03.2017 11:32Да ну глупости это :)
Зайдите на jwt.io и проверяйте токены, используя любые пары ключей.Razaz
07.03.2017 11:42Ох. Ну начнем.
Факт валидности подписи не означает что вы можете доверять этому токену.
Вы обязаны проверить что ваш audience совпадает с audience токена. Тоесть если токен выдан на https://domain1, а вы на https://domain2 — его использовать нельзя.
Так же надо проверять валидность сертификата, что бы злоумышленник не мог подписать токен отозванным сертификатом при его компрометации.
Еще Issuer забыл. Его то же.Rothmansua
07.03.2017 11:46Да да? :)
А если я сам пишу AS, RS и клиент (вы же мне этого не запретите?)
И плевать хотел на все issuer и audience (допустим у меня их «по одному»).
Что выходит мне JWT запрещено использовать? :)Razaz
07.03.2017 11:51Тогда у вас не OAuth2 а свой велосипедик, похожий на него)).
Могу только посоветовать не писать AS ;)
С таким подходом возможен как минимум misuse токенов в других приложениях, которые доверяют вашему AS.
Зачем изобретать велосипед, когда все это есть в стандартных либах? :)Rothmansua
07.03.2017 11:52OAuth2 — это протокол, а не AS или как вы выражаетесь «IdP»
Хочу — реализую, хочу — беру готовое решение.Razaz
07.03.2017 11:54AS — участник, описанный в спецификации протокола.
IdP может выполнять роль AS или STS. Тут уже и другие протоколы есть.
Есть спека, есть implementors guide, есть threat model. Ну не хотите — флаг в руки как говорится.Rothmansua
07.03.2017 11:57Да при чем тут implementor's guide?
Issuer, Audience и подобные поля имеют смысл, если в них есть что писать :)
Если у вас только 1 issuer и все токены только для одного Audience («все пользователи»), что толку заполнять эти поля?
Чем они повысят безопасность?Razaz
07.03.2017 12:54Вы обязаны в них записать эти параметры.
Audience — uri приложения. Вы наверное со скоупом попутали.
Зачем вам тогда OAuth2? :)Rothmansua
07.03.2017 13:18Я вам все-таки настойчиво рекомендую разобраться в чем разница между JWT и OAuth2.
Rothmansua
07.03.2017 11:35А, или я вообще не понял смысл комментария. Я что писал, что «Сертификат для подписи должен совпадать с сертификатом для TLS»?
Razaz
07.03.2017 11:39«Если доверяешь домену»?
Rothmansua
07.03.2017 11:44Ну да, тому откуда приложение в браузер загружается.
Если все части распределенной OAuth2 системы скомпрометированы (включая AS и клиент), то тут уже «поздно пить Боржоми».
Но TLS сертификат к механизму подписи JWT отношения не имеет.
lair
07.03.2017 11:30Почему это сертификат?
Да можно и public key с тем же успехом написать, суть не изменится.
Подпиши токен private ключем и раздай всем пользователям, кто об этом попросит, свой public key и вот они уже могут счастливо проверять подлинность JWT токенов без лишних раундтрипов к тебе же.
… потом заложи в эту схему угрозу "я продолбал private key", и все станет понятно.
Rothmansua
07.03.2017 11:34Не, ну да.
Но кстати если «хардкодить public key в яваскрипт», то при потере и последующей смене private key клиенты мало что заметят, потому что будут скачивать и использовать новый/обновленный public key.lair
07.03.2017 11:35"Скачивать". То есть обращение к серверу. О чем и была речь с самого начала.
Rothmansua
07.03.2017 11:37Ну… не совсем.
Яваскрипт-то не обязательно идет с AS, он идет с Resource Server'a.
В случае смены private/public key пары да, придется две стороны обновлять.
Но клиент все равно не затронут :)
Хотя, возможно, подробности зависят от конкретного OAuth2 flow.lair
07.03.2017 11:38Ну так речь не о том, затронут ли клиент, речь о том, сколько обращений нужно сделать, чтобы быть уверенным в валидности access token.
Rothmansua
07.03.2017 11:40Первая загрузка клиентского приложения (например SPA в браузер) все же не в счет. Этого обращения к серверу, как и первого логина с паролем, не избежать.
lair
07.03.2017 11:56… и закэшировать на тот месяц, пока пользователь браузер на закрывает? А если за это время надо инвалидировать ключи?
Rothmansua
07.03.2017 11:59Ну да, это слабое место, согласен.
lair
07.03.2017 12:01+2Это как раз типичный компромис между "временем жизни" (чего угодно, что токена, что ключа, что сертификата) и безопасностью. Больше время жизни — меньше нагрузка, меньше безопасность, и наоборот.
Veha
07.03.2017 11:12Rothmansua
07.03.2017 11:25эти поля опциональные и как бы «резервируются» для реализаций, в случае если они вдруг им понадобятся. Обязательный набор JWT полей для минимальной реализации совсем небольшой.
Razaz
07.03.2017 11:27Зато есть описание того, как их использовать с OAuth2/OpenID Connect. И там много всего интересного. Настоятельно рекомендую ознакомиться :)
lair
07.03.2017 11:28Помогает только для проверки, кому предназначен токен, а совсем не кем выдан.
Veha
07.03.2017 11:35issuer
The iss (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The iss value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.lair
07.03.2017 11:36+1И что мне мешает написать туда что угодно?
Razaz
07.03.2017 12:00В валидации как минимум присутствуют:
1. Проверка самой подписи.
2. Проверка доверя к издателю — Issuer + Key пара. По факту траст.
3. Проверка времени жизни токена.
4. Проверка Proof of posession токена.
Ну это я так, понудеть :Dlair
07.03.2017 12:02Да знаю я, знаю.
Проверка доверя к издателю — Issuer + Key пара. По факту траст.
Вот это место (выше уже обсудили до дыр) требует либо инфраструктуры, либо обращения к серверу, и требует выбора решения на пространстве "время кэширования — актуальность данных".
Razaz
07.03.2017 12:08Конечно.
Есть вариант гонять тело публичного ключа в x5c, но это будет ооочень жирный токен)).
Есть еще OIDC Discovery + WebFinger:
Я просто затягиваю дискавери документ и кеширую на небольшой срок(минут 10 например) и проблема решена. Там же и ключики приезжают.
Veha
06.03.2017 21:07В БД храним только refresh token, но обращаемся к БД только когда access token истек
Rothmansua
06.03.2017 21:13Какую БД вы имеете в виду?
- Client Application
- Resource Server
- Authorization Server
http://tutorials.jenkov.com/oauth2/roles.html
Rothmansua
06.03.2017 21:18Все что хранится не в БД, а в памяти сервера делает его stateful и умешает его scalability.
Если Client Application это SPA или мобильное приложение (один одновременный пользователь/сессия), то там нет смысла что-либо хранить «в БД» (там и БД-то обычно недоступно).
Rothmansua
06.03.2017 21:06…
Veha
06.03.2017 21:13Поставить срок жизни access token маленький например 30 мин, можно удалить в БД refresh token юзера, тогда нельзя будет получить новый access token и надо будет вводить логин пароль. Если токен не был украден то пользователь не заметит как приложение обновит access token через 30 мин, а если ктото другой использовал refresh token то они не совпадут и пользователю надо будет ввести логин пароль, в итоге украденый refresh token будет не валиден
Rothmansua
06.03.2017 21:14В БД чего?
Veha
06.03.2017 21:23в БД Authorization Server
Rothmansua
06.03.2017 21:27Ясно.
Итак, вы предлагаете, не хранить access token'ы в БД Authorization Server'а, только refresh токены.
Значит придется их хранить в памяти — хозяин барин.Veha
06.03.2017 21:28Заем access token хранить в памяти Authorization Server'а? для каких целей?
Rothmansua
06.03.2017 21:30Например чтобы поддерживать Single Logout
Razaz
06.03.2017 21:35https://openid.net/specs/openid-connect-session-1_0.html
http://openid.net/specs/openid-connect-backchannel-1_0.html
http://openid.net/specs/openid-connect-frontchannel-1_0.html
Все до безобразия просто :) Не надо токены держать для этого.
IdP/AS просто хранит в сессии список клиентов, которые в рамках нее обращались например.
Razaz
06.03.2017 21:29В определенных случаях(нет интроспекции и все клэймы в токене и клиент поддерживает backchannel логаут) может иметь право на жизнь.
Rothmansua
06.03.2017 21:31Не только «иметь право на жизнь». Без этого вооще не понятно как logout реализовывать.
Veha
06.03.2017 21:35Удалить из БД refresh token и юзер не сможет получить новый access token, надо будет вводить логин/пароль
Rothmansua
07.03.2017 01:34Ну так это не логаут, а просто expiration of refresh token.
Этот интервал обычно гораздно дольше чем expiration of access token.
Безопасность тут будет хромать на обе ноги.Rothmansua
07.03.2017 10:52Вернее expiration of access token.
Это и происходит при логауте, только оба токена удаляются/деактивируются из БД authorization server'a.
Razaz
06.03.2017 21:37Выше привел ссылки на спеки. Можно и к чистому OAuth2 AS накрутить. Вариантов достаточно.
GreenStore
06.03.2017 22:03> Итак, вы предлагаете, не хранить access token'ы в БД Authorization Server'а,
> только refresh токены. Значит придется их хранить в памяти — хозяин барин.
Как я понимаю, если токен — не случайное число, а криптографический токен, то для проверки его подлинности не нужно использовать БД и где либо вообще его хранить.
Если требуется реализовать механизм отзыва, то храниться/распространяться должны токены из черного списка, а не рабочие токены. Отзыв токена — гораздо более редкая ситуация (плохих токенов много меньше, чем рабочих), плюс к этому, срок жизни access-токена сильно ограничен.Rothmansua
06.03.2017 22:09Логаут — это и есть «механизм отзыва».
Вы предлагаете отказаться от logout'a не понятно для чего (для оригинальности?).
«срок жизни access-токена сильно ограничен.»
15 минут — это по-вашему «сильно ограничен»?
Да зачем вообще с этой аутентификацией возиться, без нее вообще ничего нигде хранить не надо.GreenStore
06.03.2017 22:13> Логаут — это и есть «механизм отзыва»
По-моему, логаут — это стирание данных о токенах на клиенте. Т.е. это операция со стороны клиента, а не сервера.Rothmansua
06.03.2017 22:17Ну это не верно.
GreenStore
06.03.2017 22:22Что бы не было бессмысленных споров, приведите ваше определение термину «логаут».
Rothmansua
06.03.2017 23:58Да любое определение лучше.
Не с азов же тут обучение начинать.
Ваше определение — как страусу спрятать голову в песок: «если я тебя не вижу, значит и ты меня не видишь». Это называется "security theater":
Потеря пароля от своего банковского счета, не усложнит жизнь взломщику, который пытается получить у нему доступ (особенно если вы его до того на бумажке всем раздавали).GreenStore
07.03.2017 05:37Я попросил вас привести определение без всякой задней мысли.
Пожалуйста, приведите ваше определение для «логаут». Иначе о чем может быть спор, ведь мы с вами должны оперировать одинаковыми терминами и понятиями.Rothmansua
07.03.2017 10:54Попробую определить логаут так:
«Прекращение доверия пользовательской сессии в лице access и refresh token'а на стороне authorization server».
Razaz
06.03.2017 22:14Думаю все просто надо собраться и почитать спеки:) Все уже придумано :D
Если токен в виде JWT, то хранить или нет просто вопрос бизнес требований — в UI показать какие-нибудь метаданные админу, статистику пособирать и тд.
Второй тип токена — это референс токен. Это по сути сгенерированный крипторандомом хэндл + обычный токен. Но токен хранится в бд, а не катается на клиента. И тут до кучи еще и интроспекция не помешает.
Отзыв access_token с IdP/AS вообще странный кейс. Проще разлогинить юзера удаленно.
tsabir
07.03.2017 10:16Мои пять копеек в кучу теорий :) Поправьте меня, если несу чушь.
Пара RefreshToken + AccessToken нужна потому что:
RefreshToken можно использовать для получения следующего Access/Refresh Token, а AccessToken нельзя. Т.е. передавая AccessToken третьим лицам (ServiceProvider) или через каналы к которым у меня нет доверия, я избегаю утечки доступа к моей сессии (IdentityProvider).
- В случаях, когда AccessToken относительно быстро истекает, чтобы получить доступ к сервису, клиенту нужно будет обязательно сходить за ним к IdentityProvider, используя RefreshToken. Получается, что если отозвать RefreshToken, то это автоматом закроет возможность получения доступа к сервису при компрометации RefreshToken. Понятно, что ServiceProvider принципиально должен принимать только AccessToken, чтобы это работало.
lair
07.03.2017 10:30+1Да нет никакого "нужна": есть больше одного сценария, когда без рефреша прекрасно обходятся. Однако если нам надо обеспечить работу в отсутствие пользователя (всякие сервисы) и при этом мы не хотим долгоживущий access token, потому что мы параноики — тогда пара refresh-access будет единственным решением.
tsabir
07.03.2017 10:34есть больше одного сценария, когда без рефреша прекрасно обходятся
не совсем понял, без рефреша как-то продлевают сессию?
lair
07.03.2017 10:36Отправляют пользователя обратно на AS. Если у него там открытая сессия (или авторизация, не требующая ввода данных) и нет необходимости подтверждать разрешение приложения, то обратно прилетает токен, пользователь может даже ничего и не заметить.
tsabir
07.03.2017 10:45Ну я бы не сказал, что этот способ прямо разительно отличается. Технически, вы просто заменили RefreshToken на Session cookie. Ну и, далеко не все клиенты в браузере.
lair
07.03.2017 10:47Технически, вы просто заменили RefreshToken на Session cookie
Нет, технически я переложил ответственность за долгосрочную сессию с клиента на AS. Сам клиент в этом случае становится существенно проще.
(а еще бывают ситуации, когда долгосрочная сессия вообще не нужна)
Ну и, далеко не все клиенты в браузере.
Я и не говорил, что этот подход всегда работает.
tsabir
07.03.2017 10:52-1Нет, технически я переложил ответственность за долгосрочную сессию с клиента на AS. Сам клиент в этом случае становится существенно проще.
Позвольте не согласиться, вы переложили ответственность на веб-браузер.
Ну и, далеко не все клиенты в браузере.
Я и не говорил, что этот подход всегда работает.
Думаю, в случае с RefreshToken предлагается более универсальный подход, нежели всякие Session Cookie и редиректы в браузерах
lair
07.03.2017 10:55Позвольте не согласиться, вы переложили ответственность на веб-браузер.
Не позволю. Аутентификацию производит именно AS, каким способом он это делает — его дело. Браузер выступает только агентом, осуществляющим перевод пользователя по шагам.
Думаю, в случае с RefreshToken предлагается более универсальный подход
… имеющий свои ограничения. Как следствие, каждый может сам оценить свои требования и понять, какой сценарий ему более выгоден.
редиректы в браузерах
Нельзя сделать OAuth без "редиректов в браузерах" (либо вам придется заставлять пользователя вводить информацию руками).
tsabir
07.03.2017 11:07Нельзя сделать OAuth без "редиректов в браузерах" (либо вам придется заставлять пользователя вводить информацию руками).
Странный аргумент. Хотите сказать, что Resource Owner Password Credentials Grant уже не является частью OAuth?
lair
07.03.2017 11:11+1Нет, я хочу сказать, что resource owner налагает совсем другие требования по безопасности.
Хотя, конечно, здесь вы правы, я и сам про него вспомнил уже.
Razaz
07.03.2017 11:45А за использование этого гранта можно и огрести. Он предполагается для совместимости с легаси приложениями.
tsabir
07.03.2017 12:07А где в OAuth 2.0 написано про легаси?
Как по мне, так авторизация в браузере как раз есть частный случай такого гранта, а resource owner просто доверяет ему хранение паролей и куков.
Также некоторые AS позволяют без браузера при этом защищают через MFA например ну или через всякие Apple Social.
Я понимаю, что вы имеете ввиду всякие Фэйсбуки и Твиттеры и их политики, но OAuth не только под них же писан в конце-концов.
Razaz
07.03.2017 12:14+1Этот грант нельзя использовать так как ваше приложение получает доступ к паролю. Весь сакральный смысл делегированной аутентификации/авторизации, что бы приложения не имели доступа к секретам пользователя.
Есть implicit flow — через него и решать.tsabir
07.03.2017 12:30А если нет возможности implicit flow? Почему вы прицепились к браузеру?
Razaz
07.03.2017 12:32Если вдруг такое случилось — то можете сделать сайлент логин, не показывая веб вью. Тут не надо аутентифицировать клиента. Но это самый самый крайний случай и его надо максимально избегать.
tsabir
07.03.2017 12:09имеющий свои ограничения
Имеете ввиду какие-то технические ограничения? Можно примеры?
lair
07.03.2017 12:17Имеете ввиду какие-то технические ограничения? Можно примеры?
Например, клиенту теперь надо реализовывать не один маршрут(нет access-токена/протух access-токен — пошли за токеном), а два с половиной: получение токенов и рефреш (включая замену рефреша, если он на рефреше обновился, простите за формулировку). Плюс рефреш надо хранить как confidential. Плюс нужна идентификация (и очень желательна аутентификация) клиента (и начинается цирк со всеми публичными клиентами).
tsabir
07.03.2017 13:02Я если честно, вижу только 1 неприятный момент — это утечка Рефреш-токена. Остальное абстрагируется же библиотекой?
lair
07.03.2017 13:05Аутентификация клиента библиотекой не абстрагируется, потому что это дополнительные действия администратора.
tsabir
07.03.2017 13:08Тут я согласен, редирект однозначно это решает.
lair
07.03.2017 13:09Эээ, я что-то не понимаю, какой редирект это решает и как.
tsabir
07.03.2017 13:19Ну я в какой-то момент так предположил, но теперь что-то засомневался.
в случае без рефреш-токена, как этот вопрос решается?
lair
07.03.2017 13:21В случае без рефреш-токена аутентификация клиента менее важна (проще говоря, иногда можно для публичных AS вообще не делать аутентификацию). Поэтому не "вопрос как-то решается", а "вопрос просто не встает".
tsabir
07.03.2017 13:29Ну вот, я это и имел ввиду. Если Аксесс-токен обновлять строго через редирект, то на клиенте не будет храниться ничего, поэтому нет смысла его защищать
Razaz
07.03.2017 12:15Не универсальный. Refresh это не сессия. Access это не сессия. Выше уже обсудили несколько раз.
tsabir
07.03.2017 12:28Вот именно, что универсальный, так как не привязан вообще к сессиям и браузерам
Razaz
07.03.2017 12:35Откуда вы это напридумывали?
Весь смысл включения рефреш токена — доступ к ресурсу. когда пользователь не имеет активной сессии.
Проимер: У вас есть клиент, который в фоне опрашивает ресурссный сервер. Когда пользователь залогинен — вы обновляете access_token редиректом на AS. Когда пользователя нет — запросом рефреш токена. Если вы сделаете рефреш токен сессией — вы делите на 0.tsabir
07.03.2017 12:38Я не могу никак понять, зачем вы все приплетаете сессии и браузеры? OAuth и OpenID Connect не ограничен ими.
Razaz
07.03.2017 12:44А http клиенты с куками не умеют работать?
Раз уж вспомнили OIDC То рекомендую ознакомиться с полным набором спек:
http://openid.net/connect/
И в частности — http://openid.net/specs/openid-connect-session-1_0.html.
И никогда не завязываться на refresh as session. Для этого там есть id_token.
tsabir
07.03.2017 12:54Если честно, я зашел в тупик.
я пишу
Думаю, в случае с RefreshToken предлагается более универсальный подход, нежели всякие Session Cookie и редиректы в браузерах
вы отвечаете:
Не универсальный
Весь смысл включения рефреш токена — доступ к ресурсу. когда пользователь не имеет активной сессии.То есть, если есть сессия, то пользоваться Рефреш-токеном для получения Аксес-токена — моветон? Зачем мне реализовывать в клиенте 2 способа, когда у меня есть Рефреш?
Razaz
07.03.2017 13:00+1Если у вас клиенту не нужен оффлайн доступ — вам не нужен рефреш. Вообще.
Просто редирект и все.
1. Сразу пропадает необходимость безопасного хранения токенов. Вы же не в открытом виде их храните так? Наверное для каждой сессии хотя бы генерите уникальный ключик и шифруете им?
2. Нет необходимости поддержки безопасного бэкченел соединения с AS.
tsabir
07.03.2017 14:23В целом я согласен, что через редирект безопаснее, но все же:
- есть Apple Keychain и его аналоги на других платформах
- не понял. HTTPS же обычно есть
Razaz
07.03.2017 14:341. Эппл — частный случай. Есть еще Android :)
2. У вас есть так называемый front channel и back channel. Запросы юзеров и клиентов приходят через front channel, а рефреш вы просите через back channel. Тут встает вопрос к инфраструктуре, её аудиту, 2-leg ssl. Если залогируют access-token — неприятно, но не смертельно. Если будут логировать refresh — полный треш. Это на вскидку :)
В том же OIDC при использовании гибридного флоу и получении id_token + access_token через from_post — вам вообще не надо ходить на IdP в простейшем случае.
Тут нет универсальных решений. Для каждого случая надо подбирать свое решение из доступных вариантов, инфраструктуры, требований.
prijutme4ty
07.03.2017 11:18Да ну, ничего не объяснили.
Боб не дурак использовать refresh token и будет ждать, пока access token протухнет. Access токен в состоянии сам следить за временем своей жизни, refresh ему для этого не нужен.
И где профит?
olegi
07.03.2017 11:36Применительно к мобильным приложениям(МП) или SDK, Oauth и токены как-то криво вписываются.
Задача: юзер вводит логин и пароль, работает в МП. Через месяц юзер снова запускает МП, и продолжает работать.
В связи с тем, что Authz(Authn)(Identity)Server поддерживают только OAuth и OpenID, эту задачу я решил так: access_token-у установил время жизни день, а refresh_token-у 3 года.
1 день жизни access_token снижает нагрузку на сервер, если выписывать его каждые 30 минут.
а имея рефреш токен позволяет отозвать сессию если юзер скомпрометирован.lair
07.03.2017 11:38Применительно к мобильным приложениям(МП) или SDK, Oauth и токены как-то криво вписываются.
Смотря для чего вы их используете. Почему криво-то?
Задача: юзер вводит логин и пароль, работает в МП. Через месяц юзер снова запускает МП, и продолжает работать.
Ну вот сразу: куда вводит логин/пароль? В мобильное приложение? Или в браузер? Что должно произойти, если за этот месяц юзер поменял пароль?
olegi
07.03.2017 12:13Смотря для чего вы их используете. Почему криво-то?
На мой взгляд, это та же cookie с сессией, только сбоку.
Но даёт сложности в виде проверки — если access_token протух, то через refresh получить новый access_token и повторить запрос. Ещё +1 обёртка над сетевыми запросами.
Ну вот сразу: куда вводит логин/пароль? В мобильное приложение?
В МП. Native наше всё. Ну, и по большому счету, имхо, нет разницы между native и браузером. Меня «радует» рекомендация PCI DSS для ввода данных банковских карт — давать вводить CC number и CVC2 в МП это не безопасно, а через webview это нормально.
Что должно произойти, если за этот месяц юзер поменял пароль?
При смене пароля можно отозвать refresh токен. А можно и не отзывать. Ведь возможность работы с функциональностью МП != от значений логина и пароля.
Т.е. все пляски из-за того, чтобы просто не ломать oauth, но при этом нарушаем РЕКОМЕНДАЦИЮ, чтобы refresh давать только server side приложениям.Razaz
07.03.2017 12:17Я в мобилках использую Implicit flow. А сессионная кука IdP просто валяется в суки контейнере.
И не нужен рефреш.
При смене пароля можете сделать логаут клиентов.
lair
07.03.2017 12:21На мой взгляд, это та же cookie с сессией, только сбоку.
Семантика другая, но регулярно так же используют. Но семантика другая.
В МП. Native наше всё.
Тогда лично вам на OAuth можно уже положить — ваше (или замаскировавшееся под ваше фишинговое) приложение уже получило логин/пароль пользователя.
Т.е. все пляски из-за того, чтобы просто не ломать oauth, но при этом нарушаем РЕКОМЕНДАЦИЮ, чтобы refresh давать только server side приложениям.
"Authorization servers MAY issue refresh tokens to web application clients and native application clients."
Skit25
07.03.2017 14:07+1Это прямо более, чем актуальная тема!
Объяснение чёткое и понятное.
Комменты полезные.
В общем, спасибо!eyeofhell
07.03.2017 14:08Торт Хабр или не торт — от нас зависит :) Присоединяйтесь!
Razaz
07.03.2017 14:10+1Вы бы хоть статью поправили. В коментах все разжевали во всех возможных вариантах.
eyeofhell
07.03.2017 14:33Предлагай что править. Сейчас там два, на мой взгляд, самых «ярких» пункта со стека (про использование токенов на разных сервисах и про раздельное хранение) плюс что мне понравилось про ограничение времени атаки. Что, на твой взгляд, можно улучшить?
Razaz
07.03.2017 14:57На стеке то же ошибаются. Еще как :) Есть спецификации, комментарии к ним. Вообще такие вопросы лучше задавать и смотреть на http://security.stackexchange.com. Но обязательно прочитав спеки, так как много очень странных ответов, которые не модерируются.
1. refresh_token- это креденшалы для доступа в API при отсутствии сессии юзера. access_token — короткоживущий токен для доступа к ресурсу.
2. Разные требования к хранению и передаче(и выдаче). Тоесть узнать refresh_token вы обычно не можете, так как он не гоняется через front channel.
3. Работу с общей сессией лучше делегировать на ваш IdP/AS/OP по выбору. Спецификации для этого в комментах привел. Там можете и следить. и ограничивать, и включать любые политики какие вам захочется.
Если у вас задача аутентификации и авторизации в API и ваши клиенты это серверные приложения, то у вас есть так называемый Client credentials flow. И вам не нужен рефреш в принципе. Просто смотрите за лайфтаймом токена и обновляете его по необходимости.
Если есть возможность использования сертификатов, то есть client assertion grant: клиент генерирует JWT токен со своей информацией и подписывает его своим приватным ключиком. Сервер валидирует его, проверяет публичную часть (что у регистрации клиента привязан этот сертификат) и если все ок, выдает уже access_token для доступа в API.
Если веб(SPA) или есть webview то implicit flow. Получаете access_token и вперед в API. Как протухнет — редирект за обновлением или тихонечко в айфрейме запросик. Можно поковырять кишки вот тут.
Если есть вопросы — спрашивайте тут или в личку.
eyeofhell
07.03.2017 15:03И вот что — это все запихивать в статью, превращая ее в Wall of text? :(
Пока дописал вот: «Например, хранить access token на frontend, а refresh token на backend»Razaz
07.03.2017 15:13Если дальше расписывать, но будет еще одна статья :) А времени на нее пока нет :( Тут уж вам посмотреть и решить как лучше. Тут коллективный разум постарался всю проблему в комментах разобрать :)
netch80
11.03.2017 09:11+1У меня претензия к именованию участников. Обычно в криптографии Алиса и Боб — две стороны защищённого взаимодействия, тут скорее Алиса держала бы сервер, выдающий токены, а Боб — законный пользователь. А воры токенов — это в первую очередь Крейг (Craig). Вот вроде полный список.
Результат — читая статью, на каждом абзаце приходится переименовывать в голове.
Данную статью уже не исправить из-за комментариев, где повторены эти имена, но на будущее прошу не сбивать в таких основах.
Blumfontein
12.03.2017 20:48+2>> В этом случае Боб получит доступ к сервису на время жизни access token. Как только оно истечет и приложение, которым пользуется Алиса, воспользуется refresh token, сервер вернет новую пару токенов, а те, что узнал Боб, превратятся в тыкву.
Пользователи с хреновым интернетом (обычно мобильным) будут постоянно ловить логауты из-за того, что запрос на рефреш ушел на сервер, а ответ не получен из-за сбоя подключения. Сервер сгенерирует новые токены, но клиент их не получит, и его (клиента) токены превратятся в тыкву. Можно не инвалидировать старые токены при генерации новых, но тогда Боб может вечно пользоваться своим рефреш-токеном.Razaz
13.03.2017 00:07Это одна из причин, почему refresh_token мобилкам не надо отдавать. Он должен кататься только между серверами. На мобилке вполне вариант хранить куку сессии с IdP/AS.
lair
Вообще-то, спецификация OAuth 2 не требует инвалидации ранее выданных токенов при выдаче нового refresh token:
eyeofhell
Звучит разумно. Если такая защита не нужна — зачем заставлять разработчиков ее делать? Поинт в том, что так можно и оно действительно неплохо защищает, если access достаточно короткоживущий.
lair
Это, гм, иллюзия. Если в качестве Алисы у вас unattended service, и Боб успешно обменял свой refresh на новый, у Алисы случился отказ в обслуживании, и сколько он продлится — зависит исключительно от того, насколько там подвижные админы. И все это время Боб будет иметь доступ к системе.
eyeofhell
Unattended это все ж таки другой кейс. И, в любом случае, он об этом сможет написать админам :) А если слился обычный токен — то никто об этом не узнает, нет каких-то сложных эвристик с IP
lair
Вот поэтому обычные токены в схеме с рефрешами делают очень короткоживущими.
(BTW, если он слился у вас, об этом точно так же никто не узнает)
eyeofhell
Об этом узнает пользователь SDK через 2-е суток, когда аппа снова попросит логин и пароль.
lair
Неа.
Если Боб использует только аксесс, у пользователя SDK обмен рефреша на новый пройдет совершенно безболезненно. Поэтому Боб может радостно использовать аксесс до того момента, пока тот не будет инвалидирован (что скорее всего будет сделано по окончанию его срока годности).
Иными словами, если хитрый Боб перехватил аксесс в момент выдачи, то у него — в вашем случае — есть двое суток на неотслеживаемый несанкционированный доступ.
eyeofhell
Двое суток — это лучше, чем два месяца. А время жизни и подкрутить можно :)
lair
… это не отменяет того милого факта, что Боб творит свои акции незаметно.
eyeofhell
Для остальных есть Diffie-Hellman, Certificate Authority, server-side challenge и прочие радости криптографии.
lair
Ну просто заявленное вами в посте не очень достигнуто.
saipr
Облачный токен PKCS#11
и аутентификации в ОС семейства «Linux» с использованием смарт-карт или USB-токенов на базе российской криптографии
dark_dwarf
А вам не кажется, что фиг с ней с Алисой (раз уж допустила утечку токенов), а гораздо важнее не допустить «учетку данных» к Бобу? И на это в стандарте даже дается намек:
Таким образом, если refresh_token использован более 1 раза (Боб обновил, а за ним и Алиса пришла), сервер вполне имеет право отозвать и ранее выданные Бобу токены (access & refresh).
lair
Нет, не кажется. Во-первых, именно Алиса приносит деньги, поэтому если она будет недовольна частыми логаутами, может быть плохо. Во-вторых, атака через отказ от обслуживания — тоже атака. И в-третьих, совершенно не факт, что утечку токенов допустила именно Алиса.
Уже допустили: как минимум на время жизни access token.
Сервер, несомненно, имеет право. Вопрос в том, будет ли он это делать — потому что это и нагрузка на сервер, и нагрузка на аналитиков при разработке эвристик. "Использование токена более одного раза" — это тоже эвристика, это может быть и в абсолютно валидной ситуации "Алиса вызвала рефреш, связь оборвалась, она попробовала еще раз".
dark_dwarf
Иногда репутационные потери гораздо выше потери нескольких не очень аккуратных клиентов. Короче зависит.
Иногда деньги приносит не Алиса, а тот ресурс, к которому Алиса «теряет ключи (токены)».
Если вам на работе выдали ключи, а вы их потеряли — вы просите новые. Так вот если с помощью «потерянных» ключей можно что-то ценное унести, то имеет смысл сменить замки (возможно даже за ваш счет, чтобы бережнее с ключами обходились).
Тут нет однозначного ответа.
Что неужели сам authorization server?
Не совсем верно. Если по факт повторного использования refresh_token-а отзываются все токены выданные ранее этому пользователю, то на время от 0 до жизни access_token — зависит как Алиса придет обновлять токены.
Это зависит, от конкретной среды для которой делается сервер. Мое замечание было о
Собственно я не согласен с утверждением «всё это время» — не всё, если сервер готов защищаться активно (в том числе от нерасторопных Алис).
lair
Много вариантов.
… а придет она по истечении access-токенов.
Не просто активно, а аггрессивно. Что, как и любое повышение безопасности, будет идти за счет снижения комфорта клиентов.
dark_dwarf
Например? OAuth2.0/OpenID оно ж только через SSL, так что если только SSL сломают.
Согласен, но ещё зависит как быстро Боб получил Алисины токены. Если вместе с ней, то да — время утечки равно времени жизни access-токена (если других мер не применяется).
абсолютно согласен.
lair
Ну да, если есть контроль за устройством, можно подсадить свой рутовый сертификат и устроить MitM. Это самое простое, что в голову приходит.
Ну, мы вроде как исходим из того, что у него есть и access, и рефреш, а выдаются они одновременно.
centur
но там есть оговорки, все же абзац весь выглядит так :
А вообще протокол часто используется не по делу =) для аутентификации, а не авторизации (хотя на фоне всех остальных протоколов аутентификации — он прост и понятен...)
Вообще есть куда более простое объяснение — момент аутентификации\авторизации — более уязвим для атакующего чем момент доступа к ресурсам..
Т.е. если Ева перехватила оба токена — то тут все довольно плохо — в зависимости от реализации — она может выдавить легального пользователя Алису. Но такой перехват — сложнее потому что нужно угадать момент.
Перехват же access Token — проще — любой запрос и токен в заголовке. Просто и ценность такого токена низкая -его хватит только до конца его времени жизни, а потом он превращается в тыкву. Если сервис например делает время жизни токена в 15 минут — то Ева сможет почитать данные только 15 минут, потом всё.
Речь конечно не идет о постоянном MitM на Алису — там никакие схемы и токены не помогут...
lair
Это оговорки про то, как клиент должен реагировать на новый выданный токен.
eyeofhell
По поводу сложности перехвата… Сейчас же все по TLS :( Это, ИМХО, уже не так актуально.
lair
Это актуально, как только вы попадаете в браузер, где перехват может быть не только в канале.
eyeofhell
Всегда хотел спросить — а где в таком браузере будет храниться refresh token? :) Ведь если скомпрометирован браузер, то он оба токена сольет. Или есть большая группа ситуаций, где браузер скомпрометирован, но refresh не сольется?
lair
А рефреш в браузере и не хранится. Он хранится на сервере, который меняет его на аксесс (обращением к авторизационному серверу), и отдает браузеру, который и идет с этим аксессом к ресурс-серверу.
В implicit flow (который используется, если у нас только браузер, без активного сервера) рефреши выдаваться не могут: "The authorization server MUST NOT issue a refresh token."
Razaz
Для аутентификации там есть OpenId Connect. Голый OAuth2 как-то уже не комильфо.
Refresh желательно выдавать только через backchannel соединение между серверами (тоесть никаких Implicit и Hybrid грантов, если говорить про OIDC).
Так же есть уже спека на Proof of Posession.
Neuyazvimy1
У нас при разработке одной игры издатель отказался нам выдать права админа в гугл плэй сервисах. Тем самым мы не могли правами админа проверять действительно ли игрок получил ачивку. Мы это решили просто, мы отправляли access token игрока нам на сервер, где сервер этим токеном проверялось валидность ачивки. Резюмируя скажу что 2 токена нужны для того чтобы: пользователь мог авторизовываться не светя свой пароль лишний раз, также для удобства обратно же пользователя, ну и для сторонних сервисов как в примере. З.Ы в этой игре у нас тоже были свои токены, access — жил 30 минут, refresh — был перманентен.