Комментарии 296
Вообще-то, спецификация OAuth 2 не требует инвалидации ранее выданных токенов при выдаче нового refresh token:
The authorization server MAY revoke the old refresh token after issuing a new refresh token to the client.
Поинт в том, что так можно и оно действительно неплохо защищает, если access достаточно короткоживущий.
Это, гм, иллюзия. Если в качестве Алисы у вас unattended service, и Боб успешно обменял свой refresh на новый, у Алисы случился отказ в обслуживании, и сколько он продлится — зависит исключительно от того, насколько там подвижные админы. И все это время Боб будет иметь доступ к системе.
А если слился обычный токен — то никто об этом не узнает, нет каких-то сложных эвристик с IP
Вот поэтому обычные токены в схеме с рефрешами делают очень короткоживущими.
(BTW, если он слился у вас, об этом точно так же никто не узнает)
Неа.
Если Боб использует только аксесс, у пользователя SDK обмен рефреша на новый пройдет совершенно безболезненно. Поэтому Боб может радостно использовать аксесс до того момента, пока тот не будет инвалидирован (что скорее всего будет сделано по окончанию его срока годности).
Иными словами, если хитрый Боб перехватил аксесс в момент выдачи, то у него — в вашем случае — есть двое суток на неотслеживаемый несанкционированный доступ.
If a refresh token is compromised and subsequently used by both the attacker and the
legitimate client, one of them will present an invalidated refresh
token, which will inform the authorization server of the breach.
Таким образом, если refresh_token использован более 1 раза (Боб обновил, а за ним и Алиса пришла), сервер вполне имеет право отозвать и ранее выданные Бобу токены (access & refresh).
А вам не кажется, что фиг с ней с Алисой (раз уж допустила утечку токенов)
Нет, не кажется. Во-первых, именно Алиса приносит деньги, поэтому если она будет недовольна частыми логаутами, может быть плохо. Во-вторых, атака через отказ от обслуживания — тоже атака. И в-третьих, совершенно не факт, что утечку токенов допустила именно Алиса.
а гораздо важнее не допустить «учетку данных» к Бобу?
Уже допустили: как минимум на время жизни access token.
Таким образом, если refresh_token использован более 1 раза (Боб обновил, а за ним и Алиса пришла), сервер вполне имеет право отозвать и ранее выданные Бобу токены (access & refresh).
Сервер, несомненно, имеет право. Вопрос в том, будет ли он это делать — потому что это и нагрузка на сервер, и нагрузка на аналитиков при разработке эвристик. "Использование токена более одного раза" — это тоже эвристика, это может быть и в абсолютно валидной ситуации "Алиса вызвала рефреш, связь оборвалась, она попробовала еще раз".
Во-первых, именно Алиса приносит деньги
Иногда репутационные потери гораздо выше потери нескольких не очень аккуратных клиентов. Короче зависит.
Иногда деньги приносит не Алиса, а тот ресурс, к которому Алиса «теряет ключи (токены)».
Если вам на работе выдали ключи, а вы их потеряли — вы просите новые. Так вот если с помощью «потерянных» ключей можно что-то ценное унести, то имеет смысл сменить замки (возможно даже за ваш счет, чтобы бережнее с ключами обходились).
Тут нет однозначного ответа.
токенов допустила именно Алиса
Что неужели сам authorization server?
Уже допустили: как минимум на время жизни access token.
Не совсем верно. Если по факт повторного использования refresh_token-а отзываются все токены выданные ранее этому пользователю, то на время от 0 до жизни access_token — зависит как Алиса придет обновлять токены.
Сервер, несомненно, имеет право. Вопрос в том, будет ли он это делать
Это зависит, от конкретной среды для которой делается сервер. Мое замечание было о
Если в качестве Алисы у вас unattended service, и Боб успешно обменял свой refresh на новый, у Алисы случился отказ в обслуживании, и сколько он продлится — зависит исключительно от того, насколько там подвижные админы. И все это время Боб будет иметь доступ к системе.
Собственно я не согласен с утверждением «всё это время» — не всё, если сервер готов защищаться активно (в том числе от нерасторопных Алис).
Что неужели сам authorization server?
Много вариантов.
зависит как Алиса придет обновлять токены.
… а придет она по истечении access-токенов.
Собственно я не согласен с утверждением «всё это время» — не всё, если сервер готов защищаться активно (в том числе от нерасторопных Алис).
Не просто активно, а аггрессивно. Что, как и любое повышение безопасности, будет идти за счет снижения комфорта клиентов.
Много вариантов.
Например? OAuth2.0/OpenID оно ж только через SSL, так что если только SSL сломают.
а придет она по истечении access-токенов.
Согласен, но ещё зависит как быстро Боб получил Алисины токены. Если вместе с ней, то да — время утечки равно времени жизни access-токена (если других мер не применяется).
Что, как и любое повышение безопасности, будет идти за счет снижения комфорта клиентов.
абсолютно согласен.
Например? OAuth2.0/OpenID оно ж только через SSL, так что если только SSL сломают.
Ну да, если есть контроль за устройством, можно подсадить свой рутовый сертификат и устроить MitM. Это самое простое, что в голову приходит.
Согласен, но ещё зависит как быстро Боб получил Алисины токены. Если вместе с ней, то да — время утечки равно времени жизни access-токена
Ну, мы вроде как исходим из того, что у него есть и access, и рефреш, а выдаются они одновременно.
но там есть оговорки, все же абзац весь выглядит так :
The authorization server MAY issue a new refresh token, in which case
the client MUST discard the old refresh token and replace it with the
new refresh token. The authorization server MAY revoke the old
refresh token after issuing a new refresh token to the client. If a
new refresh token is issued, the refresh token scope MUST be
identical to that of the refresh token included by the client in the
request.
А вообще протокол часто используется не по делу =) для аутентификации, а не авторизации (хотя на фоне всех остальных протоколов аутентификации — он прост и понятен...)
Вообще есть куда более простое объяснение — момент аутентификации\авторизации — более уязвим для атакующего чем момент доступа к ресурсам..
Т.е. если Ева перехватила оба токена — то тут все довольно плохо — в зависимости от реализации — она может выдавить легального пользователя Алису. Но такой перехват — сложнее потому что нужно угадать момент.
Перехват же access Token — проще — любой запрос и токен в заголовке. Просто и ценность такого токена низкая -его хватит только до конца его времени жизни, а потом он превращается в тыкву. Если сервис например делает время жизни токена в 15 минут — то Ева сможет почитать данные только 15 минут, потом всё.
Речь конечно не идет о постоянном MitM на Алису — там никакие схемы и токены не помогут...
но там есть оговорки, все же абзац весь выглядит так :
Это оговорки про то, как клиент должен реагировать на новый выданный токен.
Это актуально, как только вы попадаете в браузер, где перехват может быть не только в канале.
А рефреш в браузере и не хранится. Он хранится на сервере, который меняет его на аксесс (обращением к авторизационному серверу), и отдает браузеру, который и идет с этим аксессом к ресурс-серверу.
В implicit flow (который используется, если у нас только браузер, без активного сервера) рефреши выдаваться не могут: "The authorization server MUST NOT issue a refresh token."
Refresh желательно выдавать только через backchannel соединение между серверами (тоесть никаких Implicit и Hybrid грантов, если говорить про OIDC).
Так же есть уже спека на Proof of Posession.
приложение предлагает ей авторизоваться логином и паролем, сервер возвращает новую пару токенов, а те, что узнал Боб, снова превратятся в тыквуС такой схемой будет невозможно быть залогиненным на двух устройствах? Дом + работа, или ноутбук + мобильный, каждый раз нужно будет вводить пароль? Это не удобно.
использовании рефреш-токена инвалидируется ТОЛЬКО тот, который использовалсяВ таком случае для второго примера, производный токен Боба будет продолжать работать. Надо инвалидировать все производные ключи (хранить деревья). И инвалидный refresh токен можно обноаить с паролем, иначе Алиса не сможет себе его обновить.
Что бы креденшалы не вводить — на AS/Idp тупо используется SSO сессия и при протухании access_token и валидной сессии ничего вводить не надо.
Кейс Refresh token — вы дали авторизовали сервис, который автоматом делает вам красивые галереи из ваших фоток в фэйсбуке. Выгружать их он может и без вас, сидящего за компом, поэтому он попросит скоуп offline_access и будет получать токен для вызова апи когда ему надо.
Кейс Refresh token — вы дали авторизовали сервис, который автоматом делает вам красивые галереи из ваших фоток в фэйсбуке. Выгружать их он может и без вас, сидящего за компом, поэтому он попросит скоуп offline_access и будет получать токен для вызова апи когда ему надо.
Что мешает этот кейс сделать с одним токеном?
Refrsh token — это креденшалы. По которым приложение от имени пользователя, который дал разрешение на этом может получить доступ в API.
Вы уточните что вы подразумеваете под токеном и какие метаданные о нем вы храните.
2. Это не стандарт со всеми вытекающими.
1. Есть такая штука как audience. Дак вот у refresh_token audience — это ваш AS/IdP, а у access_token — список ресурсов, к которым он применим. Если используются JWT, то в него все зашивается для access_token + скоупы.
2. refresh_token — Это креденшалы для аутентификации на AS/IdP. access_token — несет в себе(или ссылается) информацию для авторизации в API.
https://tools.ietf.org/html/rfc6749#page-10
Спека с вами не согласна. скоупы там просто для того чтобы определить что можно с токеном звать а что нет, offline_access — это вообще чей то частный скоуп, в спеке ни слова про offline.
Вы случайно не путаете чью-то реализацию Oauth 2.0 (Facebook? ) с самим протоколом?
Кстати, я вот тут подумал что-то. А у вас клиент неаутентифицированный?
Логин-пароль — это вы, наверное, про пользователя говорите. А я про клиент (в смысле, программу) в терминах OAuth.
Он валидируется сервером, или кто угодно может прислать любой?
Ну то есть клиент у вас неаутентифицированный. Просто одна из степеней защиты при рефреше токена — это проверка client credentials. Вам она, в силу того, что у вас public client, она недоступна.
2. access_token — короткоживущий. Час уже достаточно большое время жизни.
refresh_token на публичном клиенте(то есть клиент не аутентифицируется сам) это фактически дырень, если у вас нет ротации токенов или какой-то хитрой эвристики, что бы 100% убедиться что это именно тот клиент которому вы его выдали.
Сценарий атаки в студию!
Для такого кейса необходимо как минимум однозначно идентифицировать клиента(конкретное устройство).
Тут уже ваше дело — доверяете ли вы Android в хранении таких критических вещей или нет.
> Случай 2: Боб узнал оба токена Алисы и воспользовался refresh
Вопрос: а в случае только одного токена разве не тоже самое можно сделать?
Копирую почти один-в-один, только с одним токеном:
Случай 1: Боб узнал токен Алисы и не воспользовался refresh
В этом случае Боб получит доступ к сервису на время жизни token. Как только оно истечет и приложение, которым пользуется Алиса, воспользуется token, сервер вернет новый токен, а тот, что узнал Боб, превратится в тыкву.
Случай 2: Боб узнал токен Алисы и воспользовался refresh
В этом случае токен Алисы превращается в тыкву, приложение предлагает ей авторизоваться логином и паролем, сервер возвращает новый токен, а тот, что узнал Боб, снова превратится в тыкву (тут есть нюанс с device id, может вернуть тот же что и у Боба. В таком случае следующее использование токена превратит токен Боба в то, что изображено справа).
Ну и зачем тогда пара токенов?
Позволять по access делать запросы в течение получаса, а регенерировать новый токен — в течение двух суток (ну или сколько там рефреш живет). По всем кейсам практически то же самое, но токен один.
Вот это единственное верное оправдание для вашего юзкейса с одним сервером. Всё что приведено в статье можно реализовать на одном токене, как верно предложил 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.
Если по access-токену можно сделать рефреш, то, перехватив access, можно дальше устроить себе сколько угодно рефрешей. Теряете в безопасности.
А что если access_token не хранить на стороне приложения, а использовать только для одной сесии и с коротким сроком жизни?
Тогда вам придется слать рефреш перед каждым запросом, что делает это эквивалентом аксесса.
Таким образом если ктото украл токены и использовал refresh_token то при запросе пользователя после инициализации или попытке обновить токены, refresh_token не совпадет с тем что в базе
А почему не совпадет?
2. Если сделана инвалидация старых рефреш токенов то будет такое поведение.
Тут вопрос в том, что если канал скомпрометирован, то поздно пить Боржоми ;)
Зачем? Как раз все правильно. Запрос на рефреш отправлять если токена нет или истекает.
Потому что если "не хранить" access-токен, то у нас всегда сценарий "токена нет".
Если сделана инвалидация старых рефреш токенов то будет такое поведение.
Ключевое слово "если". Она не обязательна.
Согласен. Но лучше ее по умолчанию включить если есть возможность :)
Я имею ввиду не хранить перзистентно :) В памяти и при закрытиии приложения удалять.
Тогда нет никакого отличия от просто короткого срока жизни. Особенно учитывая, насколько призрачны понятия "время жизни" и "закрытие приложения" на мобилке.
Т.е. повышение удобства использования (реже спрашивать пароль) без значительного понижения уровня безопасности.
«если пользователь непрерывно работает со страницей, то сессия истекает через 8 часов рабочего дня»
«если пользователь не делал никаких операций, сессия закрывается через 15 минут ожидания»
Решается это разным таймаутом валидности для access и refresh token'a.
«периодически рефрешится у IdP простым редиректом на authorize эндпоинт»
вот здесь поподробнее пожалуйста, снова пользователю пароль вводить?
Речь идет о такого рода настройке:
«если пользователь непрерывно работает со страницей, но сессия истекает через 8 часов рабочего для»
«если пользователь не делал никаких операций, сессия закрывается через 15 минут ожидания»
… и как вы это сделаете через refresh + access?
2) Через 15 минут клиент получает «отлуп» при попытке выполнить очередную операцию.
3) Вместо того чтобы снова спрашивать пользователя ввести пароль, клиент отправляет refresh token для аутентификации.
4) Refresh token валиден 8 часов
5) Повторять шаги 2-4 8 часов подряд
6) Через 8 часов попытка аутентификации с помощью refresh токена закончится неудачей и тогда уже можно спрашивать пароль по-новой
Эмм.
- У вас через восемь часов "сессия закрывается" — нужен пароль, а через 15 минут "сессия закрывается" — пароль не нужен. Немножко не одно и то же.
- Чем отличается "клиент ничего не делал 15 минут и получил отлуп по истечению access-токена" и "клиент что-то делал 15 минут и получил отлуп по истечению access-токена"?
Если есть валидный 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/
Это все хорошо, но ваше же требование "если пользователь не делал никаких операций, сессия закрывается через 15 минут ожидания" не выполнено:
- с точки зрения пользователя нет разницы, работал он 15 минут, или ничего не делал — все равно UI незаметно воспользуется рефрешем для получения нового токена
- с точки зрения системы тоже нет разницы, работал пользователь 15 минут или ничего не делал — все равно через 15 минут один токен будет заменен на другой.
При этом, что характерно, ваша пара требований прекрасно решается одним токеном со следующей комбинацией правил: скользящее время устаревания с окном в 15 минут + абсолютный максимум устаревания в 8 часов. Пользователь не работал 15 минут — все, токен протух, вперед вводить логин-пароль. Пользователь работал каждые десять минут — через восемь часов все равно вводи логин-пароль (но все эти восемь часов будет один и тот же токен, одна и та же отслеживаемая сессия).
Теперь неужели не ясна разница в уровне безопасности между двумя следующими сценариями:
1. Один и тот же access token гуляет по сети при каждом серверном запросе 8 часов (хоть следите вы на сервере за разной длинной активной/неактивной сессии, хоть не следите)
2. Access token меняется каждые 15 минут атоматически.
По вашему это равнозначно и можно обойтись только access токеном?
С точки зрения системы, если пользователь закрыл браузер и ушел никто не сможет воспользоваться перехваченным access token'om через 15 минут.
Если пользователь не закрывал браузера, все равно никто не сможет воспользоваться перехваченным токеном через 15 минут. Поэтому требование продолжает быть не выполнено.
Теперь неужели не ясна разница в уровне безопасности между двумя следующими сценариями:
Разница в уровне безопасности была понятна с самого начала. Речь идет именно о требованиях на время жизни сессии.
Вы видимо имеете в виду «HTTP сессию» на Resource Server'e. На мой взгляд, эта штука достаточно ортогональная «OAuth2 сессии», и, к тому же, которую следует избегать, чтобы не слишком усложнять дизайн системы (иначе приходится городить репликацию сессии или sticky session на load balancer'ах).
Если делать сервисы stateless и не хранить ничего пользовательского в памяти, вполне можно обойтись OAuth2 токенами для определения когда сессия, определенная ими, истекла: как только authorization server отказывается аутентифицировать access и/или refresh token переправляем пользователя на Login screen.
2. Базовое время жизни SSO сессии.
Sliding expiration называется такая штука.
Зачем? У юзера есть SSO сессия. Пока она активна IdP не будет предпринимать попыток заново аутентифицировать пользователя.
Повторюсь, OAuth2/OpenID Connect и SSO (который на чем душа пожелает можно реализовывать, хоть SAML2, хоть JWT) — две совершенно разные вещи
Можете заменить SSO на сессию IdP.
Клиент может смотреть клэймы если это JWT токен напрмиер.
JWT — это формат носителя SSO token'a
К OAuth2 и refresh token'у это [прямого] отношения не имеет.
Повторяю еще раз, мое утверждение валидно для OAuth2.
Я не утверждал и не собираюсь доказывать, что оно валидно для SSO.
Моя проблема не имеет ничего общего с SSO.
В ней речь идет об аутентификации в пределах одной системы, а не Single Sign On в несколько разных независимых.
Но смотря как вы определяете пользовательскую сессию.
Если пользовательськая сессия — это время валидности access 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
«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.
С одной стороны мы поддерживаем revocation access token'a, а с другой не обращаемся к authorization server'у и БД чтобы проверить, а не «отозван» ли наш токен :-/
в базе данных чего?
access_token не нужно хранить в базе данных,
Только в том случае, если он self-contained. Иногда — в том числе и по соображениям безопасности — access-токен все равно используется ссылочный. Это не противоречит никакому стандарту.
с помощью JWT можно хранить данные в токене — например userId, таким образом при каждом запросе к серверу мы избавляемся от лишнего запроса к базе данных так как ID юзера можно получить из токена
Не надо так делать. Используйте OIDC и identity token.
Можно конечно держать их в памяти и сравнивать, но тогда ваш Authorization Server перестает быть stateless, скалируется плохо, начинает требовать репликации данных в памяти между инстансами, возможно sticky-sessions на load balancer'e и все это только потому что мы решили не хранить его в БД.
Я это и имею в виду.
Масштабируется вполне прилично — пара инстансов на 200к+ юзеров.
Собственно это даже круто (быстрее ответ), только решение существенно сложнее (вы же не сами «ваш IdP» писали?)
А как проверять правильный ли переданный access token
Вы читали как работает JWT? access token не нужно держать на стороне сервера. Пример использования на сервере:
— создаем токен:
var token = jwt.sign({
userID: '123'
}, 'secret', { expiresIn: '1h' });
— проверяем:
var decoded = jwt.verify(token, 'secret');
// decoded.userID = 123
Теперь возвращаемся «к нашим баранам».
Итак, как верификация в вашем примере (того что JWT токен не поддельный) поможет без обращения к системе, которая его выдала, проверить отозван токен или нет?
А jwt.verify
святым духом работает? Валидность подписи вы как проверяете?
jwt.verify проверяет только что
1) токен подписан и выдан исходным authorization server
2) (опционально) он зашифрован с использованием секрета, известного вам
Если вы потеряли access токен, люой может его использовать для OAuth2 аутентифкации пока он валиден.
Теперь при взломе или досрочном прекращении работы (logout) вы должны ивалидировать access token (чтобы кто-нибудь не смог его использовать после конца вашей работы и до конца срока его действия.
При чем здесь jwt.verify() ?!
Перехваченный валидный (до тех пор пока не было логаута) OAuth2 access token может использовать любой злоумышленник для аутентификации.
Нет логаута — все ваши JWT access token'ы (что вы прицепились к этому JWT, очень много систем используют простой GUID в качестве access token'a) остаются валидными в руках злоумышленника до истечения срока их годности.
Ровно при том, что даже если мы отказались от token revocation, использование JWT все равно не позволит обойтись без обращений к серверу при проверке токена — потому что нельзя "просто взять и проверить", что токен выдан именно тем, кем утверждается, что он выдан.
(мне кажется, вы перепутали, кому я отвечал)
Но вынужден снова не согласиться. Смысл подписи и jwt.verify() именно в том, чтобы удостовериться в том, кто выдал токен, без обращения к нему.
Это свойство часто используется в схемах SSO систем без центрального/общего identy provider.
Т.е., грубо говоря, если мы отказываемся от revocation и логаута, то, похоже, можно обойтись без обращения с authorization server'у.
Смысл подписи и jwt.verify() именно в том, чтобы удостовериться в том, кто выдал токен, без обращения к нему.
Угу, и как же именно?
https://jwt.io/introduction/
Вкратце, с помощью ассиметричных алгоритмов шифрования.
Ну мы же взрослые люди, мы знаем, что в ассиметрике надо валидировать сертификат.
Resource Server обращается при каждом клиентском запросе к authorization server, чтобы убедиться, что все ок.
Но
Если вдруг authorization server недоступен (как например в сценарии SSO между двумя независимыми системами), то обращаться за проверкой некуда (например нету сетевой связи с AS, выдавшим токен).
Тут его подлинность можно проверить только с помощью криптографической подписи токена.
А зачем ему это делать каждый раз, если он кэширует актуальный сертификат CA или имеет сконфигурированный/периодически обновляемый Shared Key?
И если это необходимо, может периодически обновлять список отозванных токенов.
Кроме этого, необходимые данные authorization server легко реплицируются, т.е. система без труда горизонтально масштабируется.
Это если у вас AS и RS находятся под общим контролем.
Ассиметричное шифрование предполагает использование пары public и private ключей.
Подпиши токен private ключем и раздай private key всем пользователям, кто об этом попросит, свой public key и вот они уже могут счастливо проверять подлинность JWT токенов без лишних раундтрипов к тебе же.
(я такое делал, и именно с JWT токенами, все работает)
Если доверяешь домену, с которого скачиваешь его, то этого должно быть досточно.
Зайдите на jwt.io и проверяйте токены, используя любые пары ключей.
Факт валидности подписи не означает что вы можете доверять этому токену.
Вы обязаны проверить что ваш audience совпадает с audience токена. Тоесть если токен выдан на https://domain1, а вы на https://domain2 — его использовать нельзя.
Так же надо проверять валидность сертификата, что бы злоумышленник не мог подписать токен отозванным сертификатом при его компрометации.
Еще Issuer забыл. Его то же.
А если я сам пишу AS, RS и клиент (вы же мне этого не запретите?)
И плевать хотел на все issuer и audience (допустим у меня их «по одному»).
Что выходит мне JWT запрещено использовать? :)
Могу только посоветовать не писать AS ;)
С таким подходом возможен как минимум misuse токенов в других приложениях, которые доверяют вашему AS.
Зачем изобретать велосипед, когда все это есть в стандартных либах? :)
Хочу — реализую, хочу — беру готовое решение.
IdP может выполнять роль AS или STS. Тут уже и другие протоколы есть.
Есть спека, есть implementors guide, есть threat model. Ну не хотите — флаг в руки как говорится.
Issuer, Audience и подобные поля имеют смысл, если в них есть что писать :)
Если у вас только 1 issuer и все токены только для одного Audience («все пользователи»), что толку заполнять эти поля?
Чем они повысят безопасность?
Почему это сертификат?
Да можно и public key с тем же успехом написать, суть не изменится.
Подпиши токен private ключем и раздай всем пользователям, кто об этом попросит, свой public key и вот они уже могут счастливо проверять подлинность JWT токенов без лишних раундтрипов к тебе же.
… потом заложи в эту схему угрозу "я продолбал private key", и все станет понятно.
Но кстати если «хардкодить public key в яваскрипт», то при потере и последующей смене private key клиенты мало что заметят, потому что будут скачивать и использовать новый/обновленный public key.
"Скачивать". То есть обращение к серверу. О чем и была речь с самого начала.
Яваскрипт-то не обязательно идет с AS, он идет с Resource Server'a.
В случае смены private/public key пары да, придется две стороны обновлять.
Но клиент все равно не затронут :)
Хотя, возможно, подробности зависят от конкретного OAuth2 flow.
Ну так речь не о том, затронут ли клиент, речь о том, сколько обращений нужно сделать, чтобы быть уверенным в валидности access token.
… и закэшировать на тот месяц, пока пользователь браузер на закрывает? А если за это время надо инвалидировать ключи?
Помогает только для проверки, кому предназначен токен, а совсем не кем выдан.
И что мне мешает написать туда что угодно?
1. Проверка самой подписи.
2. Проверка доверя к издателю — Issuer + Key пара. По факту траст.
3. Проверка времени жизни токена.
4. Проверка Proof of posession токена.
Ну это я так, понудеть :D
Да знаю я, знаю.
Проверка доверя к издателю — Issuer + Key пара. По факту траст.
Вот это место (выше уже обсудили до дыр) требует либо инфраструктуры, либо обращения к серверу, и требует выбора решения на пространстве "время кэширования — актуальность данных".
- Client Application
- Resource Server
- Authorization Server
http://tutorials.jenkov.com/oauth2/roles.html
Если Client Application это SPA или мобильное приложение (один одновременный пользователь/сессия), то там нет смысла что-либо хранить «в БД» (там и БД-то обычно недоступно).
Итак, вы предлагаете, не хранить access token'ы в БД Authorization Server'а, только refresh токены.
Значит придется их хранить в памяти — хозяин барин.
http://openid.net/specs/openid-connect-backchannel-1_0.html
http://openid.net/specs/openid-connect-frontchannel-1_0.html
Все до безобразия просто :) Не надо токены держать для этого.
IdP/AS просто хранит в сессии список клиентов, которые в рамках нее обращались например.
Этот интервал обычно гораздно дольше чем expiration of access token.
Безопасность тут будет хромать на обе ноги.
> только refresh токены. Значит придется их хранить в памяти — хозяин барин.
Как я понимаю, если токен — не случайное число, а криптографический токен, то для проверки его подлинности не нужно использовать БД и где либо вообще его хранить.
Если требуется реализовать механизм отзыва, то храниться/распространяться должны токены из черного списка, а не рабочие токены. Отзыв токена — гораздо более редкая ситуация (плохих токенов много меньше, чем рабочих), плюс к этому, срок жизни access-токена сильно ограничен.
Вы предлагаете отказаться от logout'a не понятно для чего (для оригинальности?).
«срок жизни access-токена сильно ограничен.»
15 минут — это по-вашему «сильно ограничен»?
Да зачем вообще с этой аутентификацией возиться, без нее вообще ничего нигде хранить не надо.
По-моему, логаут — это стирание данных о токенах на клиенте. Т.е. это операция со стороны клиента, а не сервера.
Не с азов же тут обучение начинать.
Ваше определение — как страусу спрятать голову в песок: «если я тебя не вижу, значит и ты меня не видишь». Это называется "security theater":
Потеря пароля от своего банковского счета, не усложнит жизнь взломщику, который пытается получить у нему доступ (особенно если вы его до того на бумажке всем раздавали).
Пожалуйста, приведите ваше определение для «логаут». Иначе о чем может быть спор, ведь мы с вами должны оперировать одинаковыми терминами и понятиями.
Если токен в виде JWT, то хранить или нет просто вопрос бизнес требований — в UI показать какие-нибудь метаданные админу, статистику пособирать и тд.
Второй тип токена — это референс токен. Это по сути сгенерированный крипторандомом хэндл + обычный токен. Но токен хранится в бд, а не катается на клиента. И тут до кучи еще и интроспекция не помешает.
Отзыв access_token с IdP/AS вообще странный кейс. Проще разлогинить юзера удаленно.
А как мы можем обновлять, рефреш токен без логина/пароля ?
Мои пять копеек в кучу теорий :) Поправьте меня, если несу чушь.
Пара RefreshToken + AccessToken нужна потому что:
RefreshToken можно использовать для получения следующего Access/Refresh Token, а AccessToken нельзя. Т.е. передавая AccessToken третьим лицам (ServiceProvider) или через каналы к которым у меня нет доверия, я избегаю утечки доступа к моей сессии (IdentityProvider).
- В случаях, когда AccessToken относительно быстро истекает, чтобы получить доступ к сервису, клиенту нужно будет обязательно сходить за ним к IdentityProvider, используя RefreshToken. Получается, что если отозвать RefreshToken, то это автоматом закроет возможность получения доступа к сервису при компрометации RefreshToken. Понятно, что ServiceProvider принципиально должен принимать только AccessToken, чтобы это работало.
Да нет никакого "нужна": есть больше одного сценария, когда без рефреша прекрасно обходятся. Однако если нам надо обеспечить работу в отсутствие пользователя (всякие сервисы) и при этом мы не хотим долгоживущий access token, потому что мы параноики — тогда пара refresh-access будет единственным решением.
есть больше одного сценария, когда без рефреша прекрасно обходятся
не совсем понял, без рефреша как-то продлевают сессию?
Отправляют пользователя обратно на AS. Если у него там открытая сессия (или авторизация, не требующая ввода данных) и нет необходимости подтверждать разрешение приложения, то обратно прилетает токен, пользователь может даже ничего и не заметить.
Ну я бы не сказал, что этот способ прямо разительно отличается. Технически, вы просто заменили RefreshToken на Session cookie. Ну и, далеко не все клиенты в браузере.
Технически, вы просто заменили RefreshToken на Session cookie
Нет, технически я переложил ответственность за долгосрочную сессию с клиента на AS. Сам клиент в этом случае становится существенно проще.
(а еще бывают ситуации, когда долгосрочная сессия вообще не нужна)
Ну и, далеко не все клиенты в браузере.
Я и не говорил, что этот подход всегда работает.
Нет, технически я переложил ответственность за долгосрочную сессию с клиента на AS. Сам клиент в этом случае становится существенно проще.
Позвольте не согласиться, вы переложили ответственность на веб-браузер.
Ну и, далеко не все клиенты в браузере.
Я и не говорил, что этот подход всегда работает.
Думаю, в случае с RefreshToken предлагается более универсальный подход, нежели всякие Session Cookie и редиректы в браузерах
Позвольте не согласиться, вы переложили ответственность на веб-браузер.
Не позволю. Аутентификацию производит именно AS, каким способом он это делает — его дело. Браузер выступает только агентом, осуществляющим перевод пользователя по шагам.
Думаю, в случае с RefreshToken предлагается более универсальный подход
… имеющий свои ограничения. Как следствие, каждый может сам оценить свои требования и понять, какой сценарий ему более выгоден.
редиректы в браузерах
Нельзя сделать OAuth без "редиректов в браузерах" (либо вам придется заставлять пользователя вводить информацию руками).
Нельзя сделать OAuth без "редиректов в браузерах" (либо вам придется заставлять пользователя вводить информацию руками).
Странный аргумент. Хотите сказать, что Resource Owner Password Credentials Grant уже не является частью OAuth?
Нет, я хочу сказать, что resource owner налагает совсем другие требования по безопасности.
Хотя, конечно, здесь вы правы, я и сам про него вспомнил уже.
А где в OAuth 2.0 написано про легаси?
Как по мне, так авторизация в браузере как раз есть частный случай такого гранта, а resource owner просто доверяет ему хранение паролей и куков.
Также некоторые AS позволяют без браузера при этом защищают через MFA например ну или через всякие Apple Social.
Я понимаю, что вы имеете ввиду всякие Фэйсбуки и Твиттеры и их политики, но OAuth не только под них же писан в конце-концов.
имеющий свои ограничения
Имеете ввиду какие-то технические ограничения? Можно примеры?
Имеете ввиду какие-то технические ограничения? Можно примеры?
Например, клиенту теперь надо реализовывать не один маршрут(нет access-токена/протух access-токен — пошли за токеном), а два с половиной: получение токенов и рефреш (включая замену рефреша, если он на рефреше обновился, простите за формулировку). Плюс рефреш надо хранить как confidential. Плюс нужна идентификация (и очень желательна аутентификация) клиента (и начинается цирк со всеми публичными клиентами).
Я если честно, вижу только 1 неприятный момент — это утечка Рефреш-токена. Остальное абстрагируется же библиотекой?
Аутентификация клиента библиотекой не абстрагируется, потому что это дополнительные действия администратора.
Тут я согласен, редирект однозначно это решает.
Эээ, я что-то не понимаю, какой редирект это решает и как.
Ну я в какой-то момент так предположил, но теперь что-то засомневался.
в случае без рефреш-токена, как этот вопрос решается?
В случае без рефреш-токена аутентификация клиента менее важна (проще говоря, иногда можно для публичных AS вообще не делать аутентификацию). Поэтому не "вопрос как-то решается", а "вопрос просто не встает".
Вот именно, что универсальный, так как не привязан вообще к сессиям и браузерам
Весь смысл включения рефреш токена — доступ к ресурсу. когда пользователь не имеет активной сессии.
Проимер: У вас есть клиент, который в фоне опрашивает ресурссный сервер. Когда пользователь залогинен — вы обновляете access_token редиректом на AS. Когда пользователя нет — запросом рефреш токена. Если вы сделаете рефреш токен сессией — вы делите на 0.
Я не могу никак понять, зачем вы все приплетаете сессии и браузеры? OAuth и OpenID Connect не ограничен ими.
Раз уж вспомнили OIDC То рекомендую ознакомиться с полным набором спек:
http://openid.net/connect/
И в частности — http://openid.net/specs/openid-connect-session-1_0.html.
И никогда не завязываться на refresh as session. Для этого там есть id_token.
Если честно, я зашел в тупик.
я пишу
Думаю, в случае с RefreshToken предлагается более универсальный подход, нежели всякие Session Cookie и редиректы в браузерах
вы отвечаете:
Не универсальный
Весь смысл включения рефреш токена — доступ к ресурсу. когда пользователь не имеет активной сессии.
То есть, если есть сессия, то пользоваться Рефреш-токеном для получения Аксес-токена — моветон? Зачем мне реализовывать в клиенте 2 способа, когда у меня есть Рефреш?
Просто редирект и все.
1. Сразу пропадает необходимость безопасного хранения токенов. Вы же не в открытом виде их храните так? Наверное для каждой сессии хотя бы генерите уникальный ключик и шифруете им?
2. Нет необходимости поддержки безопасного бэкченел соединения с AS.
В целом я согласен, что через редирект безопаснее, но все же:
- есть Apple Keychain и его аналоги на других платформах
- не понял. HTTPS же обычно есть
2. У вас есть так называемый front channel и back channel. Запросы юзеров и клиентов приходят через front channel, а рефреш вы просите через back channel. Тут встает вопрос к инфраструктуре, её аудиту, 2-leg ssl. Если залогируют access-token — неприятно, но не смертельно. Если будут логировать refresh — полный треш. Это на вскидку :)
В том же OIDC при использовании гибридного флоу и получении id_token + access_token через from_post — вам вообще не надо ходить на IdP в простейшем случае.
Тут нет универсальных решений. Для каждого случая надо подбирать свое решение из доступных вариантов, инфраструктуры, требований.
Боб не дурак использовать refresh token и будет ждать, пока access token протухнет. Access токен в состоянии сам следить за временем своей жизни, refresh ему для этого не нужен.
И где профит?
Задача: юзер вводит логин и пароль, работает в МП. Через месяц юзер снова запускает МП, и продолжает работать.
В связи с тем, что Authz(Authn)(Identity)Server поддерживают только OAuth и OpenID, эту задачу я решил так: access_token-у установил время жизни день, а refresh_token-у 3 года.
1 день жизни access_token снижает нагрузку на сервер, если выписывать его каждые 30 минут.
а имея рефреш токен позволяет отозвать сессию если юзер скомпрометирован.
Применительно к мобильным приложениям(МП) или SDK, Oauth и токены как-то криво вписываются.
Смотря для чего вы их используете. Почему криво-то?
Задача: юзер вводит логин и пароль, работает в МП. Через месяц юзер снова запускает МП, и продолжает работать.
Ну вот сразу: куда вводит логин/пароль? В мобильное приложение? Или в браузер? Что должно произойти, если за этот месяц юзер поменял пароль?
Смотря для чего вы их используете. Почему криво-то?
На мой взгляд, это та же cookie с сессией, только сбоку.
Но даёт сложности в виде проверки — если access_token протух, то через refresh получить новый access_token и повторить запрос. Ещё +1 обёртка над сетевыми запросами.
Ну вот сразу: куда вводит логин/пароль? В мобильное приложение?
В МП. Native наше всё. Ну, и по большому счету, имхо, нет разницы между native и браузером. Меня «радует» рекомендация PCI DSS для ввода данных банковских карт — давать вводить CC number и CVC2 в МП это не безопасно, а через webview это нормально.
Что должно произойти, если за этот месяц юзер поменял пароль?
При смене пароля можно отозвать refresh токен. А можно и не отзывать. Ведь возможность работы с функциональностью МП != от значений логина и пароля.
Т.е. все пляски из-за того, чтобы просто не ломать oauth, но при этом нарушаем РЕКОМЕНДАЦИЮ, чтобы refresh давать только server side приложениям.
И не нужен рефреш.
При смене пароля можете сделать логаут клиентов.
На мой взгляд, это та же cookie с сессией, только сбоку.
Семантика другая, но регулярно так же используют. Но семантика другая.
В МП. Native наше всё.
Тогда лично вам на OAuth можно уже положить — ваше (или замаскировавшееся под ваше фишинговое) приложение уже получило логин/пароль пользователя.
Т.е. все пляски из-за того, чтобы просто не ломать oauth, но при этом нарушаем РЕКОМЕНДАЦИЮ, чтобы refresh давать только server side приложениям.
"Authorization servers MAY issue refresh tokens to web application clients and native application clients."
Объяснение чёткое и понятное.
Комменты полезные.
В общем, спасибо!
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. Как протухнет — редирект за обновлением или тихонечко в айфрейме запросик. Можно поковырять кишки вот тут.
Если есть вопросы — спрашивайте тут или в личку.
Пока дописал вот: «Например, хранить access token на frontend, а refresh token на backend»
У меня претензия к именованию участников. Обычно в криптографии Алиса и Боб — две стороны защищённого взаимодействия, тут скорее Алиса держала бы сервер, выдающий токены, а Боб — законный пользователь. А воры токенов — это в первую очередь Крейг (Craig). Вот вроде полный список.
Результат — читая статью, на каждом абзаце приходится переименовывать в голове.
Данную статью уже не исправить из-за комментариев, где повторены эти имена, но на будущее прошу не сбивать в таких основах.
Пользователи с хреновым интернетом (обычно мобильным) будут постоянно ловить логауты из-за того, что запрос на рефреш ушел на сервер, а ответ не получен из-за сбоя подключения. Сервер сгенерирует новые токены, но клиент их не получит, и его (клиента) токены превратятся в тыкву. Можно не инвалидировать старые токены при генерации новых, но тогда Боб может вечно пользоваться своим рефреш-токеном.
1. Пользователь ввел логин\пароль, мы проверили что все ок, создали ему два токена — первый Access в формате JWT, второй простой refresh token — набор символов?
2. Сам Access Token — самодостаточен, т.е я получая его валидирую, чтобы он был «живой», чтобы он был подписан как надо, выбираю из него userID и работаю так, будто пользователь авторизован, мне не надо лезть в базу и сверять этот токен с тем, что записано в базе, верно?
3. Когда Access Token истек, я могу отправить запрос на некий метод API вроде /token, отправить туда refresh token, проверить его с тем, что есть в базе, если в базе такой есть, значит, я обновляю в базе refresh token, генерирую новый access token и оба новых tokens возвращаю на frontend / приложение?
2. Зависит от кейса. Если SPA и Implicit Flow — то да.
3. Refresh никогда не отдается на фронт. Если вы используете Code flow, то при истечении вашей сессии, вы отдаете редирект на OP и получаете новый. Но это простейший случай.
В таком случае следующее использование refresh токена превратит токены Боба в то, что изображено справа).
я чего-то упускаю в этом моменте. Боб заполучил рефреш-токен и воспользовался им — значит получил новые аксесс- и рефреш- токены. После этого Алиса будет вынуждена ввести еще раз логин и пароль (т.к. её рефреш токеном воспользовался Боб) и после этого получит свою пару токенов. И теперь и Боб и Алиса имеют валидные рефреш токены, как будто Алиса залогинилась на разных девайсах, т.е. Боб может спокойно продолжать рефрешить аксесс токен. Что я понимаю не так?
И да — спасибо за ответ)
После логина Алисы у обоих товарищей (и у Алисы и у Боба) есть два разных, но валидных рефреш-токена. В этом случае они теперь никак не зависят друг от друга, и могут рефрешить свои токены любое кол-во раз.
На всякий случай еще уточню, как я вижу инвалидацию рефреш-токена: в момент использования рефреш-токена он становится невалидным (добавляем его в черный список, чтобы никто не смог им больше воспользоваться), а клиенту генерим и отдаем свежую пару токенов.
В вашей схеме, я полагаю, рефреш-токен Боба должен становиться невалидным после того как Алиса воспользуется своим рефреш-токеном, но это верно только тогда, когда у обоих один и тот же токен
в таком случае, Боб даже украв рефреш токен не сможет им воспользоваться вообще ни разу. Мне нравится)
Вряд ли это то, что изначально подразумевалось в вашем посте, но в итоге даже лучше получилось. Если я, конечно, опять чего-то не напутал)
В любом случае, еще раз благодарю)
The previous refresh token is invalidated
but retained by the authorization server. If a refresh token is
compromised and subsequently used by both the attacker and the
legitimate client, one of them will present an invalidated refresh
token, which will inform the authorization server of the breach
AccessToken — для короткого промежутка (не стучится в бд)
RefreshToken -> для обновления Access & RefreshToken (обновляет в бд)
— одноразовый
//Желательно хранить в Cookie+HttpOnly
Login(login, pass) = { access&refresh token } + DBAdd(refreshToken)
//если разрешается только одна сессия, то удаляются другие refreshToken-ы пользователя
SomeRequest(accessToken) = { result } (401 если access token устарел)
RefreshAccessToken(refreshToken) = { access&refresh token } + DBUpdate(refreshToken)
Logout() = удалить токены из памяти клиента + DBDelete(refreshToken)
LogoutAllDevices() = DBDelete(all refresh tokens)
Это для случая, где аккаунту разрешено несколько сессий с разных приложений/устройств
Зачем нужен Refresh Token, если есть Access Token?