Pull to refresh

Comments 296

Вообще-то, спецификация OAuth 2 не требует инвалидации ранее выданных токенов при выдаче нового refresh token:


The authorization server MAY revoke the old refresh token after issuing a new refresh token to the client.
Звучит разумно. Если такая защита не нужна — зачем заставлять разработчиков ее делать? Поинт в том, что так можно и оно действительно неплохо защищает, если access достаточно короткоживущий.
Поинт в том, что так можно и оно действительно неплохо защищает, если access достаточно короткоживущий.

Это, гм, иллюзия. Если в качестве Алисы у вас unattended service, и Боб успешно обменял свой refresh на новый, у Алисы случился отказ в обслуживании, и сколько он продлится — зависит исключительно от того, насколько там подвижные админы. И все это время Боб будет иметь доступ к системе.

Unattended это все ж таки другой кейс. И, в любом случае, он об этом сможет написать админам :) А если слился обычный токен — то никто об этом не узнает, нет каких-то сложных эвристик с IP
А если слился обычный токен — то никто об этом не узнает, нет каких-то сложных эвристик с IP

Вот поэтому обычные токены в схеме с рефрешами делают очень короткоживущими.


(BTW, если он слился у вас, об этом точно так же никто не узнает)

Об этом узнает пользователь SDK через 2-е суток, когда аппа снова попросит логин и пароль.

Неа.


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


Иными словами, если хитрый Боб перехватил аксесс в момент выдачи, то у него — в вашем случае — есть двое суток на неотслеживаемый несанкционированный доступ.

Двое суток — это лучше, чем два месяца. А время жизни и подкрутить можно :)

… это не отменяет того милого факта, что Боб творит свои акции незаметно.

Для остальных есть Diffie-Hellman, Certificate Authority, server-side challenge и прочие радости криптографии.

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

А вам не кажется, что фиг с ней с Алисой (раз уж допустила утечку токенов), а гораздо важнее не допустить «учетку данных» к Бобу? И на это в стандарте даже дается намек:

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 на Алису — там никакие схемы и токены не помогут...

но там есть оговорки, все же абзац весь выглядит так :

Это оговорки про то, как клиент должен реагировать на новый выданный токен.

По поводу сложности перехвата… Сейчас же все по TLS :( Это, ИМХО, уже не так актуально.

Это актуально, как только вы попадаете в браузер, где перехват может быть не только в канале.

Всегда хотел спросить — а где в таком браузере будет храниться refresh token? :) Ведь если скомпрометирован браузер, то он оба токена сольет. Или есть большая группа ситуаций, где браузер скомпрометирован, но refresh не сольется?

А рефреш в браузере и не хранится. Он хранится на сервере, который меняет его на аксесс (обращением к авторизационному серверу), и отдает браузеру, который и идет с этим аксессом к ресурс-серверу.


В implicit flow (который используется, если у нас только браузер, без активного сервера) рефреши выдаваться не могут: "The authorization server MUST NOT issue a refresh token."

Для аутентификации там есть OpenId Connect. Голый OAuth2 как-то уже не комильфо.

Refresh желательно выдавать только через backchannel соединение между серверами (тоесть никаких Implicit и Hybrid грантов, если говорить про OIDC).

Так же есть уже спека на Proof of Posession.
У нас при разработке одной игры издатель отказался нам выдать права админа в гугл плэй сервисах. Тем самым мы не могли правами админа проверять действительно ли игрок получил ачивку. Мы это решили просто, мы отправляли access token игрока нам на сервер, где сервер этим токеном проверялось валидность ачивки. Резюмируя скажу что 2 токена нужны для того чтобы: пользователь мог авторизовываться не светя свой пароль лишний раз, также для удобства обратно же пользователя, ну и для сторонних сервисов как в примере. З.Ы в этой игре у нас тоже были свои токены, access — жил 30 минут, refresh — был перманентен.
приложение предлагает ей авторизоваться логином и паролем, сервер возвращает новую пару токенов, а те, что узнал Боб, снова превратятся в тыкву
С такой схемой будет невозможно быть залогиненным на двух устройствах? Дом + работа, или ноутбук + мобильный, каждый раз нужно будет вводить пароль? Это не удобно.
Возможно быть залогиненным, неразделяемыми между сессиями является только токены, а не логины-пароли.
См. там в скобках про device id
Если я правильно понял, ничто не мешает нам сгенерировать 2 пары токенов: одну для работы, другую для дома
Суть в том, что при использовании рефреш-токена инвалидируется ТОЛЬКО тот, который использовался. Если у вас два рефреш токена, и вы рефрешнули только первый, второй-то никуда не делся, и остался валидным.
использовании рефреш-токена инвалидируется ТОЛЬКО тот, который использовался
В таком случае для второго примера, производный токен Боба будет продолжать работать. Надо инвалидировать все производные ключи (хранить деревья). И инвалидный refresh токен можно обноаить с паролем, иначе Алиса не сможет себе его обновить.
Тогда Бобу надо испортить/стереть токены Алисы при копировании, чтобы она сгенерировала себе новые независимые токены. Возможно она не будет деактивировать все устройства, только из-за того что с неё очередной раз запросили пароль. В результате и Боб и Алиса будут иметь рабочие, обновляемые токены.

Основной смысл 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 и будет получать токен для вызова апи когда ему надо.
Кейс Refresh token — вы дали авторизовали сервис, который автоматом делает вам красивые галереи из ваших фоток в фэйсбуке. Выгружать их он может и без вас, сидящего за компом, поэтому он попросит скоуп offline_access и будет получать токен для вызова апи когда ему надо.


Что мешает этот кейс сделать с одним токеном?
Просто токен. GUID. В иллюстративных целях назовем его «razaz token» и будем выдавать после логина.
И что вы с ним будете делать? :) Это как я понимаю ссылка на нормальный токен, который храниться на AS/IdP?
Refrsh token — это креденшалы. По которым приложение от имени пользователя, который дал разрешение на этом может получить доступ в API.

«Выгружать их он может и без вас, сидящего за компом, поэтому он попросит скоуп offline_access и будет получать токен для вызова апи когда ему надо.» < — а вот это и буду делать. Фоточки выгружать. Чем один токен при таком использовании хуже?
Какой один? access_token короткоживущий. А refresh на недели выдаваться может.
Вы уточните что вы подразумеваете под токеном и какие метаданные о нем вы храните.
Просто токен. GUID. В иллюстративных целях назовем его «razaz token» и будем выдавать после логина, хранить на стороне Authority что сочтем нужным. Чем такой токен плох для offline, о котором вы писали?
1. Вы описали обычный Refresh. Получаете референс/handle(только не гуид, а сгенерированный нормальным криптографическим RNG). Используете его для получения access_token и все.
2. Это не стандарт со всеми вытекающими.
«Основной смысл Refresh token — оффлайновый доступ» слабо коррелирует с «Это не стандарт». Либо основной смысл в том что стандарт, либо что оффлайн. Если оффлайн — то непонятно, зачем для этого два токена, когда можно обойтись одним.
Это стандартный механизм получения доступа к защищенному API при отсутствии активной сессии пользователя(пользователь offline).

1. Есть такая штука как audience. Дак вот у refresh_token audience — это ваш AS/IdP, а у access_token — список ресурсов, к которым он применим. Если используются JWT, то в него все зашивается для access_token + скоупы.
2. refresh_token — Это креденшалы для аутентификации на AS/IdP. access_token — несет в себе(или ссылается) информацию для авторизации в API.

Это вы так прозрачно намекаете, что идея двух токенов в том, чтобы сервер аппы мог использовать refresh_token без авторизации, потому что ему вроде как «доверяют»?
Еще раз:
При refresh_topken гранте аутентифицируется клиент при помощи своего способа(basic, mutal tls, client assertions) + предоставляет креденшалы пользователя в виде refresh_token, а не логин/пароль.
Это все происходит на AS/IdP, а не на сервере с API.

https://tools.ietf.org/html/rfc6749#page-10
Спека с вами не согласна. скоупы там просто для того чтобы определить что можно с токеном звать а что нет, offline_access — это вообще чей то частный скоуп, в спеке ни слова про offline.
Вы случайно не путаете чью-то реализацию Oauth 2.0 (Facebook? ) с самим протоколом?

http://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess

Причем тут фэйсбук?
Все основные AS/Idp уже его поддерживают.

топик про Oauth 2.0 не про OpenId Connect

Кстати, я вот тут подумал что-то. А у вас клиент неаутентифицированный?

Он же парой логин-пароль вначале представляется. Логин его идентифицирует, пароль — аутентифицирует. Сервер потом авторизирует :) Все по фен-шую.
Клиент!=Пользователь ;)

Логин-пароль — это вы, наверное, про пользователя говорите. А я про клиент (в смысле, программу) в терминах OAuth.

Мы используем внутри сдк еще токен устройства, который сами генерим.

Он валидируется сервером, или кто угодно может прислать любой?

Валидируется логин+пароль или access token. Device ID позволяет одному пользователю залогиниться с нескольких устройств, зачем его валидировать? Всегда же можно разобрать клиентское SDK и посмотреть, как оно считается.

Ну то есть клиент у вас неаутентифицированный. Просто одна из степеней защиты при рефреше токена — это проверка client credentials. Вам она, в силу того, что у вас public client, она недоступна.

То есть у вас комбинация из public client + refresh_token + access_token лайфтайм 2 дня?
Пока да. Если будут реалистичные кейсы почему это плохо — подкрутим. Да, вообщем-то, если придет клиент и скажет «хочу 10 минут» — ничто не помешает для его акка подкрутить. You are welcome.
1. refresh_token на публичном клиенте(то есть клиент не аутентифицируется сам) это фактически дырень, если у вас нет ротации токенов или какой-то хитрой эвристики, что бы 100% убедиться что это именно тот клиент которому вы его выдали.
2. access_token — короткоживущий. Час уже достаточно большое время жизни.

refresh_token на публичном клиенте(то есть клиент не аутентифицируется сам) это фактически дырень, если у вас нет ротации токенов или какой-то хитрой эвристики, что бы 100% убедиться что это именно тот клиент которому вы его выдали.


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

Тут уже ваше дело — доверяете ли вы Android в хранении таких критических вещей или нет.

> Случай 1: Боб узнал оба токена Алисы и не воспользовался refresh
> Случай 2: Боб узнал оба токена Алисы и воспользовался refresh

Вопрос: а в случае только одного токена разве не тоже самое можно сделать?

Копирую почти один-в-один, только с одним токеном:

Случай 1: Боб узнал токен Алисы и не воспользовался refresh

В этом случае Боб получит доступ к сервису на время жизни token. Как только оно истечет и приложение, которым пользуется Алиса, воспользуется token, сервер вернет новый токен, а тот, что узнал Боб, превратится в тыкву.

Случай 2: Боб узнал токен Алисы и воспользовался refresh

В этом случае токен Алисы превращается в тыкву, приложение предлагает ей авторизоваться логином и паролем, сервер возвращает новый токен, а тот, что узнал Боб, снова превратится в тыкву (тут есть нюанс с device id, может вернуть тот же что и у Боба. В таком случае следующее использование токена превратит токен Боба в то, что изображено справа).

Ну и зачем тогда пара токенов?
Хороший вопрос. А какое время жизни будет у этого токена? Предположим, 30 минут, как обычно у access (у нас 48 часов потому что use case специфичный). Алиса залогинилась, посмотрела фотки, выключила телефон и села в метро. Через час вышла из метро, включила телефон, запустила аппу… и аппа ее просит пароль, потому что токен протух. Не круто :)

Позволять по 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, можно дальше устроить себе сколько угодно рефрешей. Теряете в безопасности.

Ваше умозаключение верно и для refresh_token. Перехватив regresh_token — можно сколько угодно раз получать новые пары access + refresh токенов.

Ну да. Если он не отозван (вручную или автоматически).

Чтобы реже спрашивать активного пользователя его пароль (см. комментарии ниже)
А что если access_token не хранить на стороне приложения, а использовать только для одной сесии и с коротким сроком жизни? А хранить refresh_token и каждый раз при инициализации приложения получать новый access_token и refresh_token. Таким образом если ктото украл токены и использовал refresh_token то при запросе пользователя после инициализации или попытке обновить токены, refresh_token не совпадет с тем что в базе и пользователя попросят ввести логин пароль, что приведет к обновлению refresh_token в базе и украденым уже нельзя будет воспользоватся
Да, если нужно усиление безопасности — то можно хоть на каждый коннект :)
А что если access_token не хранить на стороне приложения, а использовать только для одной сесии и с коротким сроком жизни?

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


Таким образом если ктото украл токены и использовал refresh_token то при запросе пользователя после инициализации или попытке обновить токены, refresh_token не совпадет с тем что в базе

А почему не совпадет?

1. Зачем? Как раз все правильно. Запрос на рефреш отправлять если токена нет или истекает.
2. Если сделана инвалидация старых рефреш токенов то будет такое поведение.

Тут вопрос в том, что если канал скомпрометирован, то поздно пить Боржоми ;)
Зачем? Как раз все правильно. Запрос на рефреш отправлять если токена нет или истекает.

Потому что если "не хранить" access-токен, то у нас всегда сценарий "токена нет".


Если сделана инвалидация старых рефреш токенов то будет такое поведение.

Ключевое слово "если". Она не обязательна.

Я имею ввиду не хранить перзистентно :) В памяти и при закрытиии приложения удалять.

Согласен. Но лучше ее по умолчанию включить если есть возможность :)
Я имею ввиду не хранить перзистентно :) В памяти и при закрытиии приложения удалять.

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

Не спорю, что access_token должен быть короткоживущим. Чем меньше мобилка знает — тем лучше.
Refresh Token нужен для того, чтобы можно было задавать разные таймауты для «активной» сессии (когда пользователь непрерывно работает) и для «неактивной» сессии (когда ушел не закрыв браузер).

Т.е. повышение удобства использования (реже спрашивать пароль) без значительного понижения уровня безопасности.
Если клиент — браузер, то достаточно SSO + access_token. Рефреш тут не нужен. Время основной сессии контролируется на IdP, а сессия клиента короткоживущая и периодически рефрешится у IdP простым редиректом на authorize эндпоинт.
Речь идет о такого рода настройке:
«если пользователь непрерывно работает со страницей, то сессия истекает через 8 часов рабочего дня»
«если пользователь не делал никаких операций, сессия закрывается через 15 минут ожидания»

Решается это разным таймаутом валидности для access и refresh token'a.

«периодически рефрешится у IdP простым редиректом на authorize эндпоинт»
вот здесь поподробнее пожалуйста, снова пользователю пароль вводить?
Речь идет о такого рода настройке:
«если пользователь непрерывно работает со страницей, но сессия истекает через 8 часов рабочего для»
«если пользователь не делал никаких операций, сессия закрывается через 15 минут ожидания»

… и как вы это сделаете через refresh + access?

1) Access token валиден 15 минут
2) Через 15 минут клиент получает «отлуп» при попытке выполнить очередную операцию.
3) Вместо того чтобы снова спрашивать пользователя ввести пароль, клиент отправляет refresh token для аутентификации.
4) Refresh token валиден 8 часов
5) Повторять шаги 2-4 8 часов подряд
6) Через 8 часов попытка аутентификации с помощью refresh токена закончится неудачей и тогда уже можно спрашивать пароль по-новой

Эмм.


  1. У вас через восемь часов "сессия закрывается" — нужен пароль, а через 15 минут "сессия закрывается" — пароль не нужен. Немножко не одно и то же.
  2. Чем отличается "клиент ничего не делал 15 минут и получил отлуп по истечению access-токена" и "клиент что-то делал 15 минут и получил отлуп по истечению access-токена"?
Смысл 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/
Хорошая статья, спасибо!

Это все хорошо, но ваше же требование "если пользователь не делал никаких операций, сессия закрывается через 15 минут ожидания" не выполнено:


  1. с точки зрения пользователя нет разницы, работал он 15 минут, или ничего не делал — все равно UI незаметно воспользуется рефрешем для получения нового токена
  2. с точки зрения системы тоже нет разницы, работал пользователь 15 минут или ничего не делал — все равно через 15 минут один токен будет заменен на другой.

При этом, что характерно, ваша пара требований прекрасно решается одним токеном со следующей комбинацией правил: скользящее время устаревания с окном в 15 минут + абсолютный максимум устаревания в 8 часов. Пользователь не работал 15 минут — все, токен протух, вперед вводить логин-пароль. Пользователь работал каждые десять минут — через восемь часов все равно вводи логин-пароль (но все эти восемь часов будет один и тот же токен, одна и та же отслеживаемая сессия).

2. С точки зрения системы, если пользователь закрыл браузер и ушел никто не сможет воспользоваться перехваченным access token'om через 15 минут. Refresh token при этом перехватить сложнее, потому что существенно реже передается по сети.

Теперь неужели не ясна разница в уровне безопасности между двумя следующими сценариями:
1. Один и тот же access token гуляет по сети при каждом серверном запросе 8 часов (хоть следите вы на сервере за разной длинной активной/неактивной сессии, хоть не следите)
2. Access token меняется каждые 15 минут атоматически.

По вашему это равнозначно и можно обойтись только access токеном?
С точки зрения системы, если пользователь закрыл браузер и ушел никто не сможет воспользоваться перехваченным access token'om через 15 минут.

Если пользователь не закрывал браузера, все равно никто не сможет воспользоваться перехваченным токеном через 15 минут. Поэтому требование продолжает быть не выполнено.


Теперь неужели не ясна разница в уровне безопасности между двумя следующими сценариями:

Разница в уровне безопасности была понятна с самого начала. Речь идет именно о требованиях на время жизни сессии.

Потому что вы забываете, что ещё есть «сессия», поверх неё защита от CSRF. Куда ещё больше токенов?
Почему это я забываю о «сессии»?
Вы видимо имеете в виду «HTTP сессию» на Resource Server'e. На мой взгляд, эта штука достаточно ортогональная «OAuth2 сессии», и, к тому же, которую следует избегать, чтобы не слишком усложнять дизайн системы (иначе приходится городить репликацию сессии или sticky session на load balancer'ах).
Пользовательскую сессию не обязательно реализовывать в виде HTTP сессии.
Если делать сервисы stateless и не хранить ничего пользовательского в памяти, вполне можно обойтись OAuth2 токенами для определения когда сессия, определенная ими, истекла: как только authorization server отказывается аутентифицировать access и/или refresh token переправляем пользователя на Login screen.
Если клиент в браузере, то HTTP сессия у него уже есть. И для получения access можно использовать её. OAuth2 немного не для этого всё же.
1. Максимальное время продления SSO сессии.
2. Базовое время жизни SSO сессии.
Sliding expiration называется такая штука.

Зачем? У юзера есть SSO сессия. Пока она активна IdP не будет предпринимать попыток заново аутентифицировать пользователя.
OAuth2 и SSO — две разные и независимые вещи
OAuth2 обычно идет в довесок к OpenID Connect у основных провайдеров. А там и SSO обычно вкручен.
«обычно» :)
Повторюсь, OAuth2/OpenID Connect и SSO (который на чем душа пожелает можно реализовывать, хоть SAML2, хоть JWT) — две совершенно разные вещи
JWT не SSO :)

Можете заменить SSO на сессию IdP.
1) Где я сказал, что JWT это SSO?
2) Так я о том же, SSO тут ни при чем, уберите его из своих комментариев и мы с вами быстро согласимся :)
Сойдемся на том, что хватит перзистентной сессии на IdP и не надо заново ничего вводить :)
Но SSO уже в каждом нормальном IdP есть ;)
Расшифруйте IdP пожалуйста «ID Provider»?
Identity Provider. Может выступать в роли Authorization Server.
Тот который следит за временем валидности токенов (сессии) в моем случае.
За временем валидности может следить как клиент так и IdP.
Клиент может смотреть клэймы если это JWT токен напрмиер.
Вы мешаете кучу терминов в одну кучу… Очень долго раскручивать.
JWT — это формат носителя SSO token'a
К OAuth2 и refresh token'у это [прямого] отношения не имеет.

Повторяю еще раз, мое утверждение валидно для OAuth2.
Я не утверждал и не собираюсь доказывать, что оно валидно для SSO.
JWT это формат передачи Assertions. Для SSO он не сдался. Информацию о сессии IdP может просто хранить в куке.
Начинает иметь, когда access_token представлен в виде JWT и содержит exp клэйм.

Думаю надо просто взять чуток шире и добавить OIDC ;)
Описанная вами проблема решается не через refresh_token.
Refresh Token — часть OAuth2 протокола.
Моя проблема не имеет ничего общего с SSO.
В ней речь идет об аутентификации в пределах одной системы, а не Single Sign On в несколько разных независимых.
И основной кейс — это получение доступа к ресурсу, когда нет пользовательской сессии.
Я скорее не соглашусь.
Но смотря как вы определяете пользовательскую сессию.
Если пользовательськая сессия — это время валидности access token'а, то да.
Иначе — нет.
access_token — выдается обычно для обращения к ресурсу. То есть вам делегируется временный доступ куда-то. Использовать его для своей сессии некорректно. Как раз тут надо включать еще OIDC.
Извините, но это (e.g. «использовать его для своей сессии») все больше напоминает какую-то демагогию.
Предлагаю разобраться самостоятельно в спокойной обстановке.
Вы можете не иметь доступа к времени жизни этого токена. Можете использовать факт его получения, как основание для аутентификации, но время жизни сессии на клиенте тут не будет кореллировать никоем образом. Можем в личке пообщаться, но думаю дискуссия будет хорошим дополнением к статье :)
Туплю. В OAuth2 expires приезжает в респонзе. Но сути это не меняет :)
Разница между 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

Наконец-то первый правильный ответ. Да, два токена сделаны для удобства сервера.
«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'у и БД чтобы проверить, а не «отозван» ли наш токен :-/
Именно, что мы можем не поддерживать revocation access token'a. SHOULD имеет значение «хорошо бы, но если нет, то и так сойдет».
Ну да, на логаут тоже можно забить. :) (что такое revocation по-вашему?)
И вообще, зачем с этой аутентификацией возиться ;)
«access_token не нужно хранить в базе данных»
в базе данных чего?
access_token не нужно хранить в базе данных,

Только в том случае, если он self-contained. Иногда — в том числе и по соображениям безопасности — access-токен все равно используется ссылочный. Это не противоречит никакому стандарту.


с помощью JWT можно хранить данные в токене — например userId, таким образом при каждом запросе к серверу мы избавляемся от лишнего запроса к базе данных так как ID юзера можно получить из токена

Не надо так делать. Используйте OIDC и identity token.

А как проверять правильный ли переданный access token, если не хранить его в БД Authorization Server'a?
Можно конечно держать их в памяти и сравнивать, но тогда ваш Authorization Server перестает быть stateless, скалируется плохо, начинает требовать репликации данных в памяти между инстансами, возможно sticky-sessions на load balancer'e и все это только потому что мы решили не хранить его в БД.
Хранение токенов в принципе обосновано. Мы(IdP) храним все выданные токены до момента их протухания/использования. Тут зависит от настроек клиента — хочет он получать полный токен или сслыку, будет он использовать интроспекцию или нет, будет он пробрасывать токен дальше и тд.

Масштабируется вполне прилично — пара инстансов на 200к+ юзеров.
И что, все токены в памяти «вашего IdP» (а не БД) всегда хранятся?
Собственно это даже круто (быстрее ответ), только решение существенно сложнее (вы же не сами «ваш IdP» писали?)
Следующий коммент. Все токены в БД. Никакого состояния в памяти.
А как проверять правильный ли переданный access token

Вы читали как работает JWT? access token не нужно держать на стороне сервера. Пример использования на сервере:

— создаем токен:
var token = jwt.sign({
userID: '123'
}, 'secret', { expiresIn: '1h' });

— проверяем:
var decoded = jwt.verify(token, 'secret');
// decoded.userID = 123
Забыли самую малость: issuer, audience, scope.
Как я уже писал выше, JWT — это формат. К OAuth2 протоколу прямого отношения не имеет. Используется чаще для установления SSO между независимыми системами (из-за своей способности нести в себе дополнительную информацию).

Теперь возвращаемся «к нашим баранам».
Итак, как верификация в вашем примере (того что JWT токен не поддельный) поможет без обращения к системе, которая его выдала, проверить отозван токен или нет?

А jwt.verify святым духом работает? Валидность подписи вы как проверяете?

При чем здесь это?!
jwt.verify проверяет только что
1) токен подписан и выдан исходным authorization server
2) (опционально) он зашифрован с использованием секрета, известного вам
Если вы потеряли access токен, люой может его использовать для OAuth2 аутентифкации пока он валиден.

Теперь при взломе или досрочном прекращении работы (logout) вы должны ивалидировать access token (чтобы кто-нибудь не смог его использовать после конца вашей работы и до конца срока его действия.

При чем здесь jwt.verify() ?!
А и да, jwt.verify() не подразуменвает раскодирования, так что пункт 2) зачеркните там (шифрование SSO token'ов практикуется в определенных схемах SSO).
Перехваченный валидный (до тех пор пока не было логаута) 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/

Вкратце, с помощью ассиметричных алгоритмов шифрования.

Ну мы же взрослые люди, мы знаем, что в ассиметрике надо валидировать сертификат.

Токены же проверяются на сервере, поэтому организация PKI или дистрибуция Shared Key не является проблемой. Или я не понял суть вопроса?
Ну обычно да.
Resource Server обращается при каждом клиентском запросе к authorization server, чтобы убедиться, что все ок.
Но
Если вдруг authorization server недоступен (как например в сценарии SSO между двумя независимыми системами), то обращаться за проверкой некуда (например нету сетевой связи с AS, выдавшим токен).
Тут его подлинность можно проверить только с помощью криптографической подписи токена.
С чего вы взяли? Онлайн проверка используется для референс токенов. Для self-contained в большинстве ситуаций смысла нет. Ну если вы не ставите лайфтайм неадекватно большим.
Ну да, все верно (и противоречий с моим предыдущим мессаджем нет).
> Resource Server обращается при каждом клиентском запросе к authorization server, чтобы убедиться, что все ок.

А зачем ему это делать каждый раз, если он кэширует актуальный сертификат CA или имеет сконфигурированный/периодически обновляемый Shared Key?

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

Кроме этого, необходимые данные authorization server легко реплицируются, т.е. система без труда горизонтально масштабируется.
Токены могут быть self-contained(содержать все в себе) или reference(ссылка на токен, который хранится на AS).
Если речь о JWT, то видно, что они в исходном варианте self-contained.

Это если у вас AS и RS находятся под общим контролем.

Почему это сертификат?
Ассиметричное шифрование предполагает использование пары public и private ключей.
Подпиши токен private ключем и раздай private key всем пользователям, кто об этом попросит, свой public key и вот они уже могут счастливо проверять подлинность JWT токенов без лишних раундтрипов к тебе же.
(я такое делал, и именно с JWT токенами, все работает)
CRL, Chain trust, Expiry. Раздают публичную часть.
Да можно просто в Яваскрипте public key хардкодить.
Если доверяешь домену, с которого скачиваешь его, то этого должно быть досточно.
Какой паблик кей в JS??? Не достаточно. Сертификат для подписи не должен совпадать с сертификатом для TLS
Да ну глупости это :)
Зайдите на jwt.io и проверяйте токены, используя любые пары ключей.
Ох. Ну начнем.
Факт валидности подписи не означает что вы можете доверять этому токену.
Вы обязаны проверить что ваш audience совпадает с audience токена. Тоесть если токен выдан на https://domain1, а вы на https://domain2 — его использовать нельзя.
Так же надо проверять валидность сертификата, что бы злоумышленник не мог подписать токен отозванным сертификатом при его компрометации.
Еще Issuer забыл. Его то же.
Да да? :)
А если я сам пишу AS, RS и клиент (вы же мне этого не запретите?)
И плевать хотел на все issuer и audience (допустим у меня их «по одному»).
Что выходит мне JWT запрещено использовать? :)
Тогда у вас не OAuth2 а свой велосипедик, похожий на него)).

Могу только посоветовать не писать AS ;)

С таким подходом возможен как минимум misuse токенов в других приложениях, которые доверяют вашему AS.

Зачем изобретать велосипед, когда все это есть в стандартных либах? :)
OAuth2 — это протокол, а не AS или как вы выражаетесь «IdP»
Хочу — реализую, хочу — беру готовое решение.
AS — участник, описанный в спецификации протокола.
IdP может выполнять роль AS или STS. Тут уже и другие протоколы есть.

Есть спека, есть implementors guide, есть threat model. Ну не хотите — флаг в руки как говорится.
Да при чем тут implementor's guide?
Issuer, Audience и подобные поля имеют смысл, если в них есть что писать :)
Если у вас только 1 issuer и все токены только для одного Audience («все пользователи»), что толку заполнять эти поля?
Чем они повысят безопасность?
Вы обязаны в них записать эти параметры.

Audience — uri приложения. Вы наверное со скоупом попутали.

Зачем вам тогда OAuth2? :)
Я вам все-таки настойчиво рекомендую разобраться в чем разница между JWT и OAuth2.
А, или я вообще не понял смысл комментария. Я что писал, что «Сертификат для подписи должен совпадать с сертификатом для TLS»?
«Если доверяешь домену»?
Ну да, тому откуда приложение в браузер загружается.
Если все части распределенной OAuth2 системы скомпрометированы (включая AS и клиент), то тут уже «поздно пить Боржоми».
Но TLS сертификат к механизму подписи JWT отношения не имеет.
читать " и раздай public key всем пользователям, кто об этом попросит"
Почему это сертификат?

Да можно и 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.

Первая загрузка клиентского приложения (например SPA в браузер) все же не в счет. Этого обращения к серверу, как и первого логина с паролем, не избежать.

… и закэшировать на тот месяц, пока пользователь браузер на закрывает? А если за это время надо инвалидировать ключи?

Ну да, это слабое место, согласен.

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

эти поля опциональные и как бы «резервируются» для реализаций, в случае если они вдруг им понадобятся. Обязательный набор JWT полей для минимальной реализации совсем небольшой.
Зато есть описание того, как их использовать с OAuth2/OpenID Connect. И там много всего интересного. Настоятельно рекомендую ознакомиться :)

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

issuer

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.

И что мне мешает написать туда что угодно?

Ну пиши что угодно в чем проблема?

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

В валидации как минимум присутствуют:
1. Проверка самой подписи.
2. Проверка доверя к издателю — Issuer + Key пара. По факту траст.
3. Проверка времени жизни токена.
4. Проверка Proof of posession токена.

Ну это я так, понудеть :D

Да знаю я, знаю.


Проверка доверя к издателю — Issuer + Key пара. По факту траст.

Вот это место (выше уже обсудили до дыр) требует либо инфраструктуры, либо обращения к серверу, и требует выбора решения на пространстве "время кэширования — актуальность данных".

Конечно.
Есть вариант гонять тело публичного ключа в x5c, но это будет ооочень жирный токен)).
Есть еще OIDC Discovery + WebFinger:
Я просто затягиваю дискавери документ и кеширую на небольшой срок(минут 10 например) и проблема решена. Там же и ключики приезжают.

… обращение к серверу раз в десять минут, как вы могли.

Я могу))
Можно и без этого обойтись, если требования не сильно высоки. Можно на время жизни приложения закешировать.

На самом деле трешачина начинается тут https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-07 ;)
В БД храним только refresh token, но обращаемся к БД только когда access token истек
Какую БД вы имеете в виду?
  • Client Application
  • Resource Server
  • Authorization Server

http://tutorials.jenkov.com/oauth2/roles.html
Все что хранится не в БД, а в памяти сервера делает его stateful и умешает его scalability.
Если Client Application это SPA или мобильное приложение (один одновременный пользователь/сессия), то там нет смысла что-либо хранить «в БД» (там и БД-то обычно недоступно).
Как минимум можете проверить его подпись =)
Поставить срок жизни access token маленький например 30 мин, можно удалить в БД refresh token юзера, тогда нельзя будет получить новый access token и надо будет вводить логин пароль. Если токен не был украден то пользователь не заметит как приложение обновит access token через 30 мин, а если ктото другой использовал refresh token то они не совпадут и пользователю надо будет ввести логин пароль, в итоге украденый refresh token будет не валиден
в БД Authorization Server
Ясно.
Итак, вы предлагаете, не хранить access token'ы в БД Authorization Server'а, только refresh токены.
Значит придется их хранить в памяти — хозяин барин.
Например чтобы поддерживать их «отзыв» (revocation)
Например чтобы поддерживать Single Logout
https://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 просто хранит в сессии список клиентов, которые в рамках нее обращались например.
В определенных случаях(нет интроспекции и все клэймы в токене и клиент поддерживает backchannel логаут) может иметь право на жизнь.
Не только «иметь право на жизнь». Без этого вооще не понятно как logout реализовывать.
Удалить из БД refresh token и юзер не сможет получить новый access token, надо будет вводить логин/пароль
При условии, что вы не выставляете access_token на два дня как автор поста ;D
Ну так это не логаут, а просто expiration of refresh token.
Этот интервал обычно гораздно дольше чем expiration of access token.
Безопасность тут будет хромать на обе ноги.
Вернее expiration of access token.
Это и происходит при логауте, только оба токена удаляются/деактивируются из БД authorization server'a.
Выше привел ссылки на спеки. Можно и к чистому OAuth2 AS накрутить. Вариантов достаточно.
> Итак, вы предлагаете, не хранить access token'ы в БД Authorization Server'а,
> только refresh токены. Значит придется их хранить в памяти — хозяин барин.


Как я понимаю, если токен — не случайное число, а криптографический токен, то для проверки его подлинности не нужно использовать БД и где либо вообще его хранить.

Если требуется реализовать механизм отзыва, то храниться/распространяться должны токены из черного списка, а не рабочие токены. Отзыв токена — гораздо более редкая ситуация (плохих токенов много меньше, чем рабочих), плюс к этому, срок жизни access-токена сильно ограничен.
Логаут — это и есть «механизм отзыва».
Вы предлагаете отказаться от logout'a не понятно для чего (для оригинальности?).

«срок жизни access-токена сильно ограничен.»
15 минут — это по-вашему «сильно ограничен»?

Да зачем вообще с этой аутентификацией возиться, без нее вообще ничего нигде хранить не надо.
> Логаут — это и есть «механизм отзыва»

По-моему, логаут — это стирание данных о токенах на клиенте. Т.е. это операция со стороны клиента, а не сервера.
Не совсем. Есть логаут инициированный клиентом, а есть — сервером :D
Что бы не было бессмысленных споров, приведите ваше определение термину «логаут».
Да любое определение лучше.
Не с азов же тут обучение начинать.

Ваше определение — как страусу спрятать голову в песок: «если я тебя не вижу, значит и ты меня не видишь». Это называется "security theater":

Потеря пароля от своего банковского счета, не усложнит жизнь взломщику, который пытается получить у нему доступ (особенно если вы его до того на бумажке всем раздавали).
Я попросил вас привести определение без всякой задней мысли.

Пожалуйста, приведите ваше определение для «логаут». Иначе о чем может быть спор, ведь мы с вами должны оперировать одинаковыми терминами и понятиями.
Попробую определить логаут так:

«Прекращение доверия пользовательской сессии в лице access и refresh token'а на стороне authorization server».
Думаю все просто надо собраться и почитать спеки:) Все уже придумано :D

Если токен в виде JWT, то хранить или нет просто вопрос бизнес требований — в UI показать какие-нибудь метаданные админу, статистику пособирать и тд.

Второй тип токена — это референс токен. Это по сути сгенерированный крипторандомом хэндл + обычный токен. Но токен хранится в бд, а не катается на клиента. И тут до кучи еще и интроспекция не помешает.

Отзыв access_token с IdP/AS вообще странный кейс. Проще разлогинить юзера удаленно.

А как мы можем обновлять, рефреш токен без логина/пароля ?

Строго по стандарту: при выдаче нового аксесс-токена по рефреш-токену авторизационный сервер может выдать и новый рефреш-токен.

Мои пять копеек в кучу теорий :) Поправьте меня, если несу чушь.


Пара RefreshToken + AccessToken нужна потому что:


  1. RefreshToken можно использовать для получения следующего Access/Refresh Token, а AccessToken нельзя. Т.е. передавая AccessToken третьим лицам (ServiceProvider) или через каналы к которым у меня нет доверия, я избегаю утечки доступа к моей сессии (IdentityProvider).


  2. В случаях, когда 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 не только под них же писан в конце-концов.

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

Есть implicit flow — через него и решать.

А если нет возможности implicit flow? Почему вы прицепились к браузеру?

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

Имеете ввиду какие-то технические ограничения? Можно примеры?

Имеете ввиду какие-то технические ограничения? Можно примеры?

Например, клиенту теперь надо реализовывать не один маршрут(нет access-токена/протух access-токен — пошли за токеном), а два с половиной: получение токенов и рефреш (включая замену рефреша, если он на рефреше обновился, простите за формулировку). Плюс рефреш надо хранить как confidential. Плюс нужна идентификация (и очень желательна аутентификация) клиента (и начинается цирк со всеми публичными клиентами).

Я если честно, вижу только 1 неприятный момент — это утечка Рефреш-токена. Остальное абстрагируется же библиотекой?

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

Тут я согласен, редирект однозначно это решает.

Эээ, я что-то не понимаю, какой редирект это решает и как.

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


в случае без рефреш-токена, как этот вопрос решается?

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

Ну вот, я это и имел ввиду. Если Аксесс-токен обновлять строго через редирект, то на клиенте не будет храниться ничего, поэтому нет смысла его защищать

Не универсальный. Refresh это не сессия. Access это не сессия. Выше уже обсудили несколько раз.

Вот именно, что универсальный, так как не привязан вообще к сессиям и браузерам

Откуда вы это напридумывали?
Весь смысл включения рефреш токена — доступ к ресурсу. когда пользователь не имеет активной сессии.

Проимер: У вас есть клиент, который в фоне опрашивает ресурссный сервер. Когда пользователь залогинен — вы обновляете access_token редиректом на AS. Когда пользователя нет — запросом рефреш токена. Если вы сделаете рефреш токен сессией — вы делите на 0.

Я не могу никак понять, зачем вы все приплетаете сессии и браузеры? OAuth и OpenID Connect не ограничен ими.

А http клиенты с куками не умеют работать?

Раз уж вспомнили 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.

В целом я согласен, что через редирект безопаснее, но все же:


  1. есть Apple Keychain и его аналоги на других платформах
  2. не понял. HTTPS же обычно есть
1. Эппл — частный случай. Есть еще Android :)
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 ему для этого не нужен.
И где профит?
Применительно к мобильным приложениям(МП) или SDK, Oauth и токены как-то криво вписываются.
Задача: юзер вводит логин и пароль, работает в МП. Через месяц юзер снова запускает МП, и продолжает работать.

В связи с тем, что 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 приложениям.
Я в мобилках использую Implicit flow. А сессионная кука IdP просто валяется в суки контейнере.
И не нужен рефреш.

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

Семантика другая, но регулярно так же используют. Но семантика другая.


В МП. Native наше всё.

Тогда лично вам на OAuth можно уже положить — ваше (или замаскировавшееся под ваше фишинговое) приложение уже получило логин/пароль пользователя.


Т.е. все пляски из-за того, чтобы просто не ломать oauth, но при этом нарушаем РЕКОМЕНДАЦИЮ, чтобы refresh давать только server side приложениям.

"Authorization servers MAY issue refresh tokens to web application clients and native application clients."

Вот тут вам для девайсов кстати: https://self-issued.info/?p=1642
Это прямо более, чем актуальная тема!
Объяснение чёткое и понятное.
Комменты полезные.

В общем, спасибо!
Торт Хабр или не торт — от нас зависит :) Присоединяйтесь!
Вы бы хоть статью поправили. В коментах все разжевали во всех возможных вариантах.
Предлагай что править. Сейчас там два, на мой взгляд, самых «ярких» пункта со стека (про использование токенов на разных сервисах и про раздельное хранение) плюс что мне понравилось про ограничение времени атаки. Что, на твой взгляд, можно улучшить?
На стеке то же ошибаются. Еще как :) Есть спецификации, комментарии к ним. Вообще такие вопросы лучше задавать и смотреть на 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. Как протухнет — редирект за обновлением или тихонечко в айфрейме запросик. Можно поковырять кишки вот тут.

Если есть вопросы — спрашивайте тут или в личку.

И вот что — это все запихивать в статью, превращая ее в Wall of text? :(
Пока дописал вот: «Например, хранить access token на frontend, а refresh token на backend»
Если дальше расписывать, но будет еще одна статья :) А времени на нее пока нет :( Тут уж вам посмотреть и решить как лучше. Тут коллективный разум постарался всю проблему в комментах разобрать :)
267 комментов и продолжает обсуждаться :)
Тема очень объемная. Много кейсов, спек, рекомендаций. Сами спеки позволяют что-то либо делать либо нет. Плюс многие крупные вендоры не всегда грамотно имплементируют спеки, что еще добавляет энтропии.

У меня претензия к именованию участников. Обычно в криптографии Алиса и Боб — две стороны защищённого взаимодействия, тут скорее Алиса держала бы сервер, выдающий токены, а Боб — законный пользователь. А воры токенов — это в первую очередь Крейг (Craig). Вот вроде полный список.
Результат — читая статью, на каждом абзаце приходится переименовывать в голове.
Данную статью уже не исправить из-за комментариев, где повторены эти имена, но на будущее прошу не сбивать в таких основах.

>> В этом случае Боб получит доступ к сервису на время жизни access token. Как только оно истечет и приложение, которым пользуется Алиса, воспользуется refresh token, сервер вернет новую пару токенов, а те, что узнал Боб, превратятся в тыкву.

Пользователи с хреновым интернетом (обычно мобильным) будут постоянно ловить логауты из-за того, что запрос на рефреш ушел на сервер, а ответ не получен из-за сбоя подключения. Сервер сгенерирует новые токены, но клиент их не получит, и его (клиента) токены превратятся в тыкву. Можно не инвалидировать старые токены при генерации новых, но тогда Боб может вечно пользоваться своим рефреш-токеном.
Это одна из причин, почему refresh_token мобилкам не надо отдавать. Он должен кататься только между серверами. На мобилке вполне вариант хранить куку сессии с IdP/AS.
Вопрос может странный но! Судя по статье — https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/

1. Пользователь ввел логин\пароль, мы проверили что все ок, создали ему два токена — первый Access в формате JWT, второй простой refresh token — набор символов?

2. Сам Access Token — самодостаточен, т.е я получая его валидирую, чтобы он был «живой», чтобы он был подписан как надо, выбираю из него userID и работаю так, будто пользователь авторизован, мне не надо лезть в базу и сверять этот токен с тем, что записано в базе, верно?

3. Когда Access Token истек, я могу отправить запрос на некий метод API вроде /token, отправить туда refresh token, проверить его с тем, что есть в базе, если в базе такой есть, значит, я обновляю в базе refresh token, генерирую новый access token и оба новых tokens возвращаю на frontend / приложение?

1. Access может быть в формате референс, тогда он то же просто набор символов. А так да.
2. Зависит от кейса. Если SPA и Implicit Flow — то да.
3. Refresh никогда не отдается на фронт. Если вы используете Code flow, то при истечении вашей сессии, вы отдаете редирект на OP и получаете новый. Но это простейший случай.
Понимаю, что пишу в старую тему, но вдруг…
В таком случае следующее использование refresh токена превратит токены Боба в то, что изображено справа).

я чего-то упускаю в этом моменте. Боб заполучил рефреш-токен и воспользовался им — значит получил новые аксесс- и рефреш- токены. После этого Алиса будет вынуждена ввести еще раз логин и пароль (т.к. её рефреш токеном воспользовался Боб) и после этого получит свою пару токенов. И теперь и Боб и Алиса имеют валидные рефреш токены, как будто Алиса залогинилась на разных девайсах, т.е. Боб может спокойно продолжать рефрешить аксесс токен. Что я понимаю не так?
Может спокойно продолжать до тех пор, пока Алиса не попробует сделать рефреш, не залогинится и не сделает следующий рефреш. Залогиниться — ключевой момент.
Алиса попробует сделать рефреш — эта операция не пройдет, потому что по её рефреш-токену рефреш сделал Боб, тогда Алисе придется по новой логиниться — до этого момента все понятно, все логично. Но сам логин не подразумевает инвалидацию других рефреш-токенов для пользователя Алиса, иначе бы вполне валидный кейс логина на разных устройствах, приводил бы к сбросу авторизаций на всех устройствах. Если бы при логине сбрасывались все доступные рефреш-токены Алисы тогда было бы понятно как токены Боба превращаются в овощ типа тыква, но, повторюсь, это не выглядит разумным решением
И да — спасибо за ответ)
Логин с последующим рефрешем. Если Боб успеет сделать рефреш между логином Алисы и ее реферешем — то статус кво сохранится. Полагаю, это степень защиты клиенты, если, после неудачного рефреша он делает логин и сразу рефреш.
Есть какая-то вещь, которая, видимо, очевидна для вас и не доступна для меня(
После логина Алисы у обоих товарищей (и у Алисы и у Боба) есть два разных, но валидных рефреш-токена. В этом случае они теперь никак не зависят друг от друга, и могут рефрешить свои токены любое кол-во раз.
На всякий случай еще уточню, как я вижу инвалидацию рефреш-токена: в момент использования рефреш-токена он становится невалидным (добавляем его в черный список, чтобы никто не смог им больше воспользоваться), а клиенту генерим и отдаем свежую пару токенов.
В вашей схеме, я полагаю, рефреш-токен Боба должен становиться невалидным после того как Алиса воспользуется своим рефреш-токеном, но это верно только тогда, когда у обоих один и тот же токен
Я не спец по безопасности, чтобы ответить точно. Полагаю, сервер приложения по IP, логинам и используемым access/refresh токенам недопускает ситуацию, когда «у Алисы и у Боба есть два разных, но валидных рефреш-токена».
А как же тогда быть Алисе, если она желает пользоваться ресурсом на двух и более устройствах параллельно? т.е. вообще исключив Боба получим ситуацию когда Алиса получив доступ к ресурсу на одном устройстве разлогинивается на всех других?
Напомню, что боб на втором устройсте не логинился, он не знает логина и пароля. И сервер тоже знает, что боб на втором устройстве не логинился.
Если я правильно понял, вы предлагаете вместе с токеном хранить некую информацию об устройстве запросившем токен (хотя бы IP-адрес) и разрешать пользоваться этим токеном только с этого же устройства?
в таком случае, Боб даже украв рефреш токен не сможет им воспользоваться вообще ни разу. Мне нравится)
Вряд ли это то, что изначально подразумевалось в вашем посте, но в итоге даже лучше получилось. Если я, конечно, опять чего-то не напутал)
В любом случае, еще раз благодарю)
Сервера обычно хранят такую информацию.
Можно хранить как IP что с точки безопасности весьма хорошо, но не удобно для пользователя. Но лучше в момент логина привязывать к каждому рефреш токену Browser Fingerprint и проверять его на соответствие при рефреше.

Браузерам вообще не рекомендуется выдавать refresh, там предпочтителен Implicit Grant.

Вы упускаете, что когда Алиса воспользуется тем же рефреш-токеном, который уже использовал Боб — то выданные Бобу токены могут быть инвалидированы сервером (при должных усилиях), об этом косвенно говорит стандарт:


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 с конкретным приложением. Для случая если кто-то украл оба токена, чтобы при Login украденные стали невалидные.
Это для случая, где аккаунту разрешено несколько сессий с разных приложений/устройств
единственное непонятно, как связывается refresh token с конкретным приложением.

По client credentials обычно.

Sign up to leave a comment.