JWT (JSON Web Token) автоматическое продление срока действия

Я хотел бы реализовать аутентификацию на основе JWT для нашего нового REST API. Но поскольку срок действия установлен в токене, можно ли его автоматически продлить? Я не хочу, чтобы пользователям нужно было входить через каждые X минут, если они активно использовали приложение в этот период. Это был бы огромный провал UX.

но продление срока действия создает новый токен (и старый по-прежнему действителен до истечения срока действия). И создание нового токена после каждого запроса звучит глупо для меня. Звучит как проблема безопасности, когда несколько токенов действительны одновременно. Конечно, я мог бы аннулировать старый используемый, используя черный список, но мне нужно будет хранить токены. И одним из преимуществ JWT является отсутствие хранения.

Я нашел, как Auth0 решил это. Они используют не только токен JWT, но и токен обновления: https://docs.auth0.com/refresh-token

но опять же, чтобы реализовать это (без Auth0), мне нужно будет хранить токены обновления и поддерживайте их срок годности. В чем же тогда реальная выгода? Почему бы не иметь только один токен (не JWT) и сохранить срок действия на сервере?

есть ли другие варианты? Использование JWT не подходит для этого сценария?

9 ответов


Я работаю в Auth0, и я участвовал в разработке функции токена обновления.

все зависит от типа приложения и вот наш рекомендуемый подход.

web-приложений

хорошим шаблоном является обновление токена до его истечения.

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

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

мобильные/приложения

большинство родных приложений войти один раз и только один раз.

идея в том, что токен обновления никогда не истекает, и это может быть всегда обменивается на действительный JWT.

проблема с токеном, который никогда не истекает, заключается в том, что никогда значит никогда. Что делать, если вы потеряете свой телефон? Таким образом, он должен быть идентифицирован пользователем каким-то образом, и приложение должно предоставить способ отозвать доступ. Мы решили использовать имя устройства, например "iPad maryo". Затем пользователь может перейти в приложение и отозвать доступ к "iPad maryo".

другой подход-отозвать токен обновления на конкретное событие. Интересное событие-смена пароля.

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


в случае, когда вы сами обрабатываете auth (i.e не используйте провайдера, такого как Auth0), может работать следующее:

  1. выпустить токен JWT с относительно коротким сроком действия, скажем, 15min.
  2. приложение проверяет срок действия токена перед любой транзакцией, требующей токена (Токен содержит срок действия). Если токен истек, он сначала просит API "обновить" токен (это делается прозрачно для UX).
  3. API получает запрос на обновление маркера, но сначала проверяет базу данных пользователей, установлен ли флаг "reauth" для этого профиля пользователя (маркер может содержать идентификатор пользователя). Если флаг присутствует, то обновление маркера запрещено, в противном случае выдается новый маркер.
  4. повторить.

флаг "reauth" в бэкэнде базы данных будет установлен, когда, например, пользователь сбросил свой пароль. Флаг удаляется при следующем входе пользователя в систему.

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


я возился при перемещении наших приложений в HTML5 с RESTful apis в бэкэнде. Решение, которое я придумал, было:

  1. клиенту выдается токен со временем сеанса 30 минут (или независимо от обычного времени сеанса на стороне сервера) при успешном входе в систему.
  2. таймер на стороне клиента создается для вызова службы для обновления маркера до истечения срока его действия. Новый токен заменит существующие в будущих вызовах.

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

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

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


альтернативным решением для аннулирования JWTs без дополнительного безопасного хранения на бэкэнде является реализация нового jwt_version столбец integer в таблице users. Если пользователь хочет выйти из системы или истечь срок действия существующих токенов, они просто увеличивают


хороший вопрос - и есть богатство информации в самом вопросе.

статьи обновить токены: когда их использовать и как они взаимодействуют с JWTs дает хорошую идею для этого сценария. Некоторые моменты:-

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

посмотрите auth0 / angular-jwt в AngularJS

для веб-API. читать включить токены обновления OAuth в приложении AngularJS с помощью ASP .NET Web API 2 и Owin


Я фактически реализовал это в PHP, используя клиент Guzzle, чтобы сделать клиентскую библиотеку для api, но концепция должна работать для других платформ.

в основном, я выпускаю два токена, короткий (5 минут) и длинный, который истекает через неделю. Клиентская библиотека использует промежуточное ПО для попытки обновления короткого маркера, если он получает ответ 401 на какой-либо запрос. Затем он попробует исходный запрос снова, и если он смог обновить, получит правильный ответ, прозрачно для пользователя. Если это не удалось, он просто отправит 401 пользователю.

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

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

Late edit: перечитывая этот месяц после того, как он был свеж в моей голове, я должен указать, что вы можете отменить доступ при обновлении короткого токена, потому что это дает возможность для более дорогих вызовов (например, вызов в базу данных, чтобы узнать, был ли пользователь запрещен), не платя за него при каждом вызове к вашим услугам.


jwt-autorefresh

если вы используете node (React / Redux / Universal JS), вы можете установить npm i -S jwt-autorefresh.

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

полный пример реализации

import autorefresh from 'jwt-autorefresh'

/** Events in your app that are triggered when your user becomes authorized or deauthorized. */
import { onAuthorize, onDeauthorize } from './events'

/** Your refresh token mechanism, returning a promise that resolves to the new access tokenFunction (library does not care about your method of persisting tokens) */
const refresh = () => {
  const init =  { method: 'POST'
                , headers: { 'Content-Type': `application/x-www-form-urlencoded` }
                , body: `refresh_token=${localStorage.refresh_token}&grant_type=refresh_token`
                }
  return fetch('/oauth/token', init)
    .then(res => res.json())
    .then(({ token_type, access_token, expires_in, refresh_token }) => {
      localStorage.access_token = access_token
      localStorage.refresh_token = refresh_token
      return access_token
    })
}

/** You supply a leadSeconds number or function that generates a number of seconds that the refresh should occur prior to the access token expiring */
const leadSeconds = () => {
  /** Generate random additional seconds (up to 30 in this case) to append to the lead time to ensure multiple clients dont schedule simultaneous refresh */
  const jitter = Math.floor(Math.random() * 30)

  /** Schedule autorefresh to occur 60 to 90 seconds prior to token expiration */
  return 60 + jitter
}

let start = autorefresh({ refresh, leadSeconds })
let cancel = () => {}
onAuthorize(access_token => {
  cancel()
  cancel = start(access_token)
})

onDeauthorize(() => cancel())

отказ от ответственности: я хранитель


Как насчет такого подхода:

  • для каждого запроса клиента сервер сравнивает время истечения токена с (currentTime-lastAccessTime)
  • Если expirationTime , он изменяет последнее lastAccessedTime на currentTime.
  • в случае бездействия в браузере в течение времени, превышающего время экспирации, или в случае, если окно браузера было закрыто и время истечения > (currentTime - lastAccessedTime), а затем сервер может истечь токен и попросить пользователя снова войти в систему.

нам не требуется дополнительная конечная точка для обновления токена в этом случае. Был бы признателен за любой feedack.


я решил эту проблему, добавив переменную в данные токена:

softexp - I set this to 5 mins (300 seconds)

Я expiresIn опция для моего желаемого времени, прежде чем пользователь будет вынужден снова войти в систему. Мой установлен на 30 минут. Это должно быть больше, чем значение softexp.

когда мое клиентское приложение отправляет запрос на сервер API (где требуется токен, например. страница списка клиентов), сервер проверяет, является ли отправленный токен действительным или нет на основе его первоначального срока действия (expiresIn) значение. Если это недопустимо, сервер ответит со статусом, определенным для этой ошибки, например. INVALID_TOKEN.

если токен все еще действителен на основе expiredIn значение, но оно уже превысило softexp значение, сервер ответит с отдельным статусом для этой ошибки, например. EXPIRED_TOKEN:

(Math.floor(Date.now() / 1000) > decoded.softexp)

на стороне клиента, если он получил EXPIRED_TOKEN response, он должен автоматически обновлять токен, отправляя запрос на обновление на сервер. Это прозрачно для пользователь и автоматически заботится о клиентском приложении.

метод обновления на сервере должен проверить, действителен ли токен:

jwt.verify(token, secret, (err, decoded) => {})

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