Добавляем Refresh Token


    В прошлой статье я рассказывал про основы JWT. Если на пальцах, то это просто ключ, с помощью которого мы открываем дверь к приватным ресурсам. А что, если этот ключ украдут (точнее, сделают дубликат). Тогда кто-то еще сможет входить на сервер под вашим именем, причём мы об этом можем даже не узнать. Такого сценария мы не хотим допустить. Но что делать?


    Повышаем безопасность


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


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


    1. Использование https протокола, который обеспечивает защиту канала передачи данных через интернет. По сути, это обертка над http, которая накладывает дополнительные криптографические протоколы — SSL и TLS
    2. Добавление в payload информации об IP. Тогда токен с других IP не пройдет проверку. Но IP можно подделать, да и что делать с динамическими IP адресами или, когда пользователь подключается с телефона в кафешке или метро. Поэтому мы не будем использовать данный подход.
    3. Вместо HS256 использовать RS256. Это обеспечивает безопасность самого секретного ключа. Но с токенами все остается абсолютно как было. RS256 нам нужен, когда мы опасаемся передавать секретный ключ другим серверам, которые могут быть ненадежными. Мы им даем только инструмент проверки подлинности токена, что абсолютно бесполезно для злоумышленника.
    4. Использовать короткоживущие токены. Но тогда пользователю придется перелогиниваться каждый раз, когда у него истечет срок жизни. Пользователю это рано или поздно надоест и он уйдет с нашего ресурса.
    5. А что если всё-равно использовать короткоживущие токены, но дать ему еще один токен, цель которого лишь в том, чтобы получить новый короткоживущий токен без новой авторизации? Такой токен называется Refresh-токен и использовать его можно будет только один раз. Об этом и будет моя статья.

    Вспомним, что такое JWT


    JWT использует преимущества подхода цифровой подписи JWS (Signature) и кодирования JWE (Encrypting). Подпись не дает кому-то подделать токен без информации о секретном ключе, а кодирование защищает от прочтения данных третьими лицами.


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


    Аутентифика́ция (англ. authentication; от греч. αὐθεντικός [authentikos] – реальный, подлинный; от αὐθέντης [authentes] – автор) — процедура проверки подлинности. В нашем случае, мы проверяем логин + пароль на совпадение с записью в базе даных пользователей.

    Авториза́ция (англ. authorization — разрешение, уполномочивание) — предоставление пользователю прав на выполнение определённых действий; а также процесс проверки (подтверждения) данных прав при попытке выполнения этих действий.

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

    Виды токенов


    • Токены доступа (JWT) — это токены, с помощью которых можно получить доступ к защищенным ресурсам. Они короткоживущие, но многоразовые. В них может содержаться дополнительная информация, напрмер, время жизни или IP-адрес, откуда идет запрос. Все зависит от желания разработчика.
    • Рефреш токен (RT) — эти токены выполняют только одну специфичную задачу — получение нового токена доступа. И на этот раз без сервера авторизации не обойтись. Они долгоживущие, но одноразовые.

    Основной сценарий использования такой: как только старый JWT истекает, то с ним мы уже не можем получить приватные данные, тогда отправляем RT и нам приходит новая пара JWT+RT. С новым JWT мы снова можем обращаться к приватным ресурсам. Конечно, рефреш токен тоже может протухнуть, но случится это не скоро, поскольку живет он намного дольше своего собрата.



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


    const validateToken = token => {
        const [ header, payload, signature ] = token.split('.');
        return signature === HS256(`${header}.${payload}`, SECRET_KEY);
    }

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


    В заключение


    Благодаря такому подходу мы уменьшаем задержку по времени обращения к серверу latency, да и сама серверная логика становится сильно проще. А с точки зрения безопасности, если у нас всё-таки украли токен доступа, то воспользоваться им смогут только ограниченное время — не больше времени его жизни. Чтобы злоумышленник смог пользоваться дольше — ему потребуется украсть еще и рефреш, но тогда настоящий пользователь узнает, что его взломали, поскольку его выкинет из системы. И стоит такому пользователю снова войти в систему, он получит обновленную пару JWT+RT, а украденные превратятся в тыкву.


    Полезные ссылки


    1. Зачем нужен Refresh Token, если есть Access Token?
    2. Refresh Tokens: When to Use Them and How They Interact with JWTs
    3. More OAuth 2.0 Surprises: The Refresh Token
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 26

      0
      >> Чтобы злоумышленник смог пользоваться дольше — ему потребуется украсть еще и рефреш, но тогда настоящий пользователь узнает, что его взломали, поскольку его выкинет из системы.

      Немного не понятен механизм проверки на компрометацию рефреш токена. Не могли бы, подсказать где прочитать или может в двух словах?
        +2
        Нет никакой проверки. Невозможно узнать, был он скомпрометирован, или же нет.
        На самом деле смысл такой.
        У вас есть Access Token (в нашем случае JWT) и Refresh Token (любой).
        Access Token — короткоживущий (например, час; можно реализовать через exp), его можно не записывать в базу, проверяется только по подписи.
        Refresh Token — живет дольше (например, 30 дней), записан в базу и проверяется по наличию в ней.
        Когда нужно сгенерировать новый Access Token:
        — берём Refresh Token из запроса
        — ищем его в базе и получаем код связанного с ним пользователя
        — удаляем текущий Refresh Token из базы
        — генерируем новую пару токенов
        — новый Refresh Token записываем в базу.
        Если кто-то украдет из клиентского приложения только Access Token, он проживет тот же час и протухнет.
        Если кто-то украдет оба токена, то через час одна копия приложения успешно получит новую пару токенов, а вторую выкинет, так как такого Refresh Token уже нет в базе.
          0
          все верно. И тогда пользователь увидит, что его выкинуло и что-то заподозрит.
          Также довольно сложно обсуждать рефреш токен без обсуждения токена со sliding expiration, потому-что вроде как это два подхода к решению проблемы короткой жизни access token.
          Если коротко, то для решения этой проблемы можно использовать либо долгоживущие access токены, либо токены со sliding expiration. Это значит, что при каждом обращении к серверу время жизни токена обновляется. То есть он протухает, например, за 10 минут, но именно за 10 минут бездействия.
          Проблема такого подхода в том, что если пользователя удалять или урежут его права, то он будет продолжать ходить с существующим access токеном все-еще со старыми правами неограниченное время. Для этого и ввели refresh токены, т.к. чтобы дропануть пользователя, достаточно просто удалить его из бд
            0

            Ещё проблема — нужна постоянная активность пользователя, либо какие-то keepalive-запросы.

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

            Так а если обновится утекший токен? У злоумышленника будет рабочий токен, хоть и не надолго.
              0

              Да. Это зависит от времени жизни токена, да и вообще меньшее зло. Либо запрашивать сервер авторизации на каждый чих, либо допустить кратковременный доступ для злоумышленника.
              В конце концов, идеальной защиты не существует. Если кого-то захотят взломать — взломают.

          0

          deleted

            0
            смысл в RT если можно точно так же послать на какой-нибудь ендпоинт JWT и вам вернут новый JWT? :) таким образом старый украденый JWT перестанет быть валидным.
              0
              старый не перестанет быть валидным, т.к. он все-еще расшифровывается подписью, а это ровно то, что и проверяется
                0
                ничего не понял, а как вы тогда инвалидируете по своей схеме JWT? он же и через тысячу милионов лет будет расшифровываться в таком случаи :)
                  0
                  Есть короткий Access токен, который никак не развалидировать, так-что в хучшем случае там 10 минут еще с ним походят, есть refresh токен, по которому обычно можно получить Access токен. Но рефрешь токен также хранится в базе данных для этого пользователя, например. И когда по рефрешь токену просят обновить access токен проверяется, что пришедший токен и токен в базе совпадают, выдается новый access и новый refresh, в базе обновляется. Соответственно если кто-то зашел под твоим логино-паролем или с твоим рефрешь токеном, то тот, который остался у тебя падает, т.к. в базе лежит уже другой. Ну или если нужно удалить/порезать пользователя в правах, то его токен дропается с базы и ему нужно перезайти (соответственно его текущий токен с его старыми правами становится не валидным)
                  Не знаю, в статье просто как-то этот процесс не очень подробно описан (а в комментарии передать тяжело)
                    0
                    Каким образом инвалидируется еще действующий access token? Т.е. пользователя урезали, но пока жив его старый access token он может пользоваться старыми правами. Как это решается?
                      0
                      Только коротким временем действия Access-токена, больше никак не ограничено.
              0

              Не совсем по теме вопрос.


              HS256 vs RS256
              Если на стороне клиента не требуется проверять подпись,
              то что дает (в плане безопасности, скорости и т.д.) rs256?
              Имеет ли смысл ограничиться HS512?


              Спасибо.

                +2
                RS256 — асимметричный, имеет смысл если у вас отдельно вынесенный сервер авторизации. У сервера авторизации есть секретный ключ для подписи токена, а на прикладном сервере только парный открытый ключ для проверки подписи.
                Если прикладной сервер взломают, то всё равно не смогут генерировать корректно подписанные токены. Сервер авторизации при этом может быть один на группу прикладных и, соответственно, более защищённым, поскольку к нему обращаются только прикладные серверы.
                  0

                  Условия:
                  Авторизации как таковой у меня нет.
                  Я использую JWT, разумеется, чтобы не хранить у себя состояние, которое я не могу восстановить, если я его не храню или мне его не вернут.
                  Отдельного сервиса с ключом у меня тоже нет, можно считать, что каждый сервис хранит ключ у себя и вопрос взлома не стоит.


                  Использование:
                  Один раз я отдаю токен с данными клиенту, второй раз, если они его устраивают,
                  он мне возвращает токен, я проверяю время и подпись, затем применяю эти данные.


                  Вывод:
                  Не имея в наличие отдельного сервиса, который создавал бы токены, выгоды в RS256 я не вижу.(В моем случае)

                    0
                    Так если вы не используете авторизацию, то и токены вам особо не нужны.
                    Без авторизации всем пользователям доступнs все данные и функции и нет смысла в подписи токена. Полезная же нагрузка без кодирования в base64 займёт в полтора раза меньше места.
                      0

                      У меня Stateless сервис, для этого мне и нужны JWT токены.
                      Насчет base64 — пока оставил в таком виде, чтобы не городить велосипеды.

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

                  А если пользователь залогинен с нескольких устройств, его тоже выбросит?

                    +1
                    Пока токены на скомпрометированы, каждое устройство будет иметь свою пару токенов (Access + Refresh). Как только будет обнаружен неверный Refresh-токен, все Refresh-токены этого пользователя будут аннулированы и на всех устройствах придётся перелогиниться по истечению срока действия их Access-токенов.
                      0

                      Смотря как настроено.
                      Самый лучший вариант — показывать текущие сессии пользователя, с IP и GeoIP-данными, и иметь возможность закрыть любую из них (удалив Refresh Token).

                      0
                      Насколько я понял в предлагаемой схеме клиентское приложение самостоятельно отслеживает истек JWT или нет и, в случае необходимости, запрашивает RT. А что если у пользователя некорректно установлено время в системе и JWT при каждом запросе будет считаться истекшим? Не правильнее ли будет на клиенте запрашивать RT при получении от сервера ошибки «Токен истек»?
                      Т.е. основной сценарий использования такой: клиент запрашивает ресурс с JWT и если сервер возвращает ошибку «Токен истек», тогда клиент отправляет RT и нам приходит новая пара JWT+RT. С новым JWT мы снова можем обращаться к приватным ресурсам.
                      Или я неправильно понял и имелась ввиду именно такая схема?
                        0
                        Такой вариант тоже возможен и он должен использоваться не вместо, а наравне с основным. Случай, когда токен уже протух — обязательно должен обрабатываться. И не стоит бояться неправильно установленного времени, потому что Date.now() всегда отработает корректно.
                          0
                          А разве Date.now() информацию берет не с установленного у пользователя времени? Если я выставлю на своем компе некорректную дату и время, разве результат будет правильным?

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

                      Самое читаемое