Шпаргалки по безопасности: JWT



    Многие приложения используют JSON Web Tokens (JWT), чтобы позволить клиенту идентифицировать себя для дальнейшего обмена информацией после аутентификации.

    JSON Web Token – это открытый стандарт (RFC 7519), который определяет компактный и автономный способ безопасной передачи информации между сторонами в виде объекта JSON.


    Эта информация является проверенной и надежной, потому что она имеет цифровую подпись.
    JWT могут быть подписаны с использованием секретного (с помощью алгоритма HMAC) или пары открытого / секретного ключей с использованием RSA или ECDSA.

    JSON Web Token используется для передачи информации, касающейся личности и характеристик клиента. Этот «контейнер» подписывается сервером, чтобы клиент не вмешивался в него и не мог изменить, например, идентификационные данные или какие-либо характеристики (например, роль с простого пользователя на администратора или изменить логин клиента).

    Этот токен создается в случае успешной аутентификации и проверяется сервером перед началом выполнения каждого клиентского запроса. Токен используется приложением в качестве “удостоверения личности” клиента (контейнер со всей информацией о нем). Сервер же имеет возможность проверять действительность и целостность токена безопасным способом. Это позволяет приложению быть stateless (stateless приложение не сохраняет данные клиента, сгенерированные за один сеанс для использования в следующем сеансе с этим клиентом (каждый сеанс выполняется независимо)), а процессу аутентификации независимым от используемых сервисов (в том смысле, что технологии клиента и сервера могут различаться, включая даже транспортный канал, хотя наиболее часто используется HTTP).

    Соображения по поводу использования JWT


    Даже если токен JWT прост в использовании и позволяет предоставлять сервисы (в основном REST) без сохранения состояния (stateless), такое решение подходит не для всех приложений, потому что оно поставляется с некоторыми оговорками, как, например, вопрос хранения токена.

    Если приложение не должно быть полностью stateless, то можно рассмотреть возможность использования традиционной системы сессий, предоставляемой всеми веб-платформами. Однако для stateless приложений JWT – это хороший вариант, если он правильно реализован.

    Проблемы и атаки, связанные с JWT


    Использование алгоритма хеширования NONE


    Подобная атака происходит, когда злоумышленник изменяет токен, а также меняет алгоритм хеширования (поле “alg”), чтобы указать через ключевое слово none, что целостность токена уже проверена. Некоторые библиотеки рассматривали токены, подписанные с помощью алгоритма none, как действительный токен с проверенной подписью, поэтому злоумышленник мог изменить полезную нагрузку (payload) токена, и приложение доверяло бы токену.

    Для предотвращения атаки необходимо использовать библиотеку JWT, которая не подвержена данной уязвимости. Также во время проверки валидности токена необходимо явно запросить использование ожидаемого алгоритма.

    Пример реализации:

    // Ключ HMAC хранится как String в памяти JVM
    private transient byte[] keyHMAC = ...;
    
    ...
    
    // Создание контекста верификации для запроса к токену
    // Явно указывается использование HMAC-256 хеш-алгоритма
    JWTVerifier verifier = JWT.require(Algorithm.HMAC256(keyHMAC)).build();
    
    // Верификация токена
    DecodedJWT decodedToken = verifier.verify(token);

    Перехват токенов


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

    Защита заключается в добавлении «пользовательского контекста» в токен. Пользовательский контекст будет состоять из следующей информации:

    1. Случайная строка, которая генерируется на этапе аутентификации и включена в токен, а также отправлена клиенту в виде более защищенного cookie (флаги: HttpOnly + Secure + SameSite + cookie префиксы).
    2. Хеш на алгоритме SHA256 от случайной строки будет сохранен в токене, чтобы любая проблема XSS не позволила злоумышленнику прочитать значение случайной строки и установить ожидаемый файл cookie.

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

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

    Код для создания токена после успешной аутентификации:

    // Ключ HMAC хранится как String в памяти JVM
    private transient byte[] keyHMAC = ...;
    // Генератор случайных данных
    private SecureRandom secureRandom = new SecureRandom();
    
    ...
    
    // Генерация случайной строки, которая является фингерпринтом пользователя
    byte[] randomFgp = new byte[50];
    secureRandom.nextBytes(randomFgp);
    String userFingerprint = DatatypeConverter.printHexBinary(randomFgp);
    
    // Добавление фингерпринта в cookie
    String fingerprintCookie = "__Secure-Fgp=" + userFingerprint
                               + "; SameSite=Strict; HttpOnly; Secure";
    response.addHeader("Set-Cookie", fingerprintCookie);
    
    // SHA256 хеш от фингерпринта для хранения хеша фингерпринта в токене
    // (вместо открытых данных) чтобы XSS не мог прочитать фингерпринт и
    // самостоятельно установить нужное значение cookie
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    byte[] userFingerprintDigest = digest.digest(userFingerprint.getBytes("utf-8"));
    String userFingerprintHash = DatatypeConverter.printHexBinary(userFingerprintDigest);
    
    // Создание токена с временем валидности 15 минут и контекстом клиента
    Calendar c = Calendar.getInstance();
    Date now = c.getTime();
    c.add(Calendar.MINUTE, 15);
    Date expirationDate = c.getTime();
    Map<String, Object> headerClaims = new HashMap<>();
    headerClaims.put("typ", "JWT");
    String token = JWT.create().withSubject(login)
       .withExpiresAt(expirationDate)
       .withIssuer(this.issuerID)
       .withIssuedAt(now)
       .withNotBefore(now)
       .withClaim("userFingerprint", userFingerprintHash)
       .withHeader(headerClaims)
       .sign(Algorithm.HMAC256(this.keyHMAC));


    Код для проверки валидности токена:
    
    // Ключ HMAC хранится как String в памяти JVM
    private transient byte[] keyHMAC = ...;
    
    ...
    
    // Получение фингерпринта пользователя из cookie
    String userFingerprint = null;
    if (request.getCookies() != null && request.getCookies().length > 0) {
     List<Cookie> cookies = Arrays.stream(request.getCookies()).collect(Collectors.toList());
     Optional<Cookie> cookie = cookies.stream().filter(c -> "__Secure-Fgp"
                                                .equals(c.getName())).findFirst();
     if (cookie.isPresent()) {
       userFingerprint = cookie.get().getValue();
     }
    }
    
    // Вычисление SHA256 хеша от полученного фингерпринта из cookie для
    // сравнения с хешем фингерпринта в токене
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    byte[] userFingerprintDigest = digest.digest(userFingerprint.getBytes("utf-8"));
    String userFingerprintHash = DatatypeConverter.printHexBinary(userFingerprintDigest);
    
    // Создание контекста для верификации токена
    JWTVerifier verifier = JWT.require(Algorithm.HMAC256(keyHMAC))
                                  .withIssuer(issuerID)
                                  .withClaim("userFingerprint", userFingerprintHash)
                                  .build();
    
    // Верификация токена
    DecodedJWT decodedToken = verifier.verify(token);

    Явное аннулирование токена пользователем


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

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

    В черном списке будет храниться сборник (в кодировке SHA-256 в HEX) токена с датой аннулирования, которая должна превышать срок действия выданного токена.

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

    Пример реализации:

    Хранилище черного списка:
    Для централизованного хранения черного списка будет использоваться база данных со следующей структурой:

    create table if not exists revoked_token(jwt_token_digest varchar(255) primary key,
    revokation_date timestamp default now());

    Управление аннулированиями токенов:

    // Контролирование отката токена (logout).
    // Используйте БД, чтобы разрешить нескольким экземплярам проверять
    // отозванный токен и разрешить очистку на уровне централизованной БД.
    public class TokenRevoker {
    
     // Подключение к БД
     @Resource("jdbc/storeDS")
     private DataSource storeDS;
    
     // Проверка является ли токен отозванным
     public boolean isTokenRevoked(String jwtInHex) throws Exception {
         boolean tokenIsPresent = false;
         if (jwtInHex != null && !jwtInHex.trim().isEmpty()) {
             // Декодирование токена
             byte[] cipheredToken = DatatypeConverter.parseHexBinary(jwtInHex);
    
             // Вычисление SHA256 от токена
             MessageDigest digest = MessageDigest.getInstance("SHA-256");
             byte[] cipheredTokenDigest = digest.digest(cipheredToken);
             String jwtTokenDigestInHex = DatatypeConverter.printHexBinary(cipheredTokenDigest);
    
             // Поиск токена в БД
             try (Connection con = this.storeDS.getConnection()) {
                 String query = "select jwt_token_digest from revoked_token where jwt_token_digest = ?";
                 try (PreparedStatement pStatement = con.prepareStatement(query)) {
                     pStatement.setString(1, jwtTokenDigestInHex);
                     try (ResultSet rSet = pStatement.executeQuery()) {
                         tokenIsPresent = rSet.next();
                     }
                 }
             }
         }
    
         return tokenIsPresent;
     }
    
    // Добавление закодированного в HEX токена в таблица отозванных токенов
    public void revokeToken(String jwtInHex) throws Exception {
         if (jwtInHex != null && !jwtInHex.trim().isEmpty()) {
             // Декодирование токена
             byte[] cipheredToken = DatatypeConverter.parseHexBinary(jwtInHex);
    
             // Вычисление SHA256 от токена
             MessageDigest digest = MessageDigest.getInstance("SHA-256");
             byte[] cipheredTokenDigest = digest.digest(cipheredToken);
             String jwtTokenDigestInHex = DatatypeConverter.printHexBinary(cipheredTokenDigest);
    
             // Проверка на наличие токена уже в БД и занесение в БД в
    	   // обратном случае
             if (!this.isTokenRevoked(jwtInHex)) {
                 try (Connection con = this.storeDS.getConnection()) {
                     String query = "insert into revoked_token(jwt_token_digest) values(?)";
                     int insertedRecordCount;
                     try (PreparedStatement pStatement = con.prepareStatement(query)) {
                         pStatement.setString(1, jwtTokenDigestInHex);
                         insertedRecordCount = pStatement.executeUpdate();
                     }
                     if (insertedRecordCount != 1) {
                         throw new IllegalStateException("Number of inserted record is invalid," + " 1 expected but is " + insertedRecordCount);
                     }
                 }
             }
    
         }
     }
    

    Раскрытие информации о токене


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

    Способ защиты достаточно очевиден и заключается в шифровании токена. Также важно защитить зашифрованные данные от атак с использованием криптоанализа. Для достижения всех этих целей используется алгоритм AES-GCM, который обеспечивает аутентифицированное шифрование с ассоциированными данными (Authenticated Encryption with Associated Data – AEAD). Примитив AEAD обеспечивает функциональность симметричного аутентифицированного шифрования. Реализации этого примитива защищены от адаптивных атак на основе подобранного шифртекста. При шифровании открытого текста можно дополнительно указать связанные данные, которые должны быть аутентифицированы, но не зашифрованы.

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

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

    Хранение токенов на стороне клиента


    Если приложение хранит токен так, что возникает одна или несколько из следующих ситуаций:

    • токен автоматически отправляется браузером (сookie storage);
    • токен получается, даже если браузер перезапущен (использование контейнера localStorage браузера);
    • токен получается в случае атаки XSS (сookie, доступный для кода JavaScript или токен, который хранится в localStorage или sessionStorage).

    Для предотвращения атаки:

    1. Хранить токен в браузере, используя контейнер sessionStorage.
    2. Добавить его в заголовок Authorization, используя схему Bearer. Заголовок должен выглядеть следующим образом:

      Authorization: Bearer <token>
    3. Добавить fingerprint информацию к токену.

    Сохраняя токен в контейнере sessionStorage, он предоставляет токен для кражи в случае XSS. Однако fingerprint, добавленный в токен, предотвращает повторное использование украденного токена злоумышленником на его компьютере. Чтобы закрыть максимум областей использования для злоумышленника, добавьте Политику безопасности содержимого браузера (Content Security Policy), чтобы ограничить контекст выполнения.

    Остается случай, когда злоумышленник использует контекст просмотра пользователя в качестве прокси-сервера, чтобы использовать целевое приложение через легитимного пользователя, но Content Security Policy может предотвратить связь с непредвиденными доменами.

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

    Использование слабого ключа при создании токена


    Если секрет, используемый в случае алгоритма HMAC-SHA256, необходимый для подписи токена, является слабым, то он может быть взломан (подобран c помощью атаки грубой силы). В результате злоумышленник может подделать произвольный действительный токен с точки зрения подписи.

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

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

    Например:

    A&'/}Z57M(2hNg=;LE?~]YtRMS5(yZ<vcZTA3N-($>2j:ZeX-BGftaVk`)jKP~q?,jk)EMbgt*kW'

    Для оценки сложности секретного ключа, используемого для вашей подписи токена, вы можете применить атаку по словарю паролей к токену в сочетании с JWT API.
    Акрибия
    99,47
    Компания
    Поделиться публикацией

    Похожие публикации

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

      –3
      Статью не читай
      @
      сразу отвечай
      Отзыв токена?
        0

        Заминусовали зря, но статья предлагает решение в разделе "Явное аннулирование токена пользователем"

        0
        Кто может объяснить, где правильно (с точки зрения безопасности) хранить JWT токены — access и refresh?
        в localStorage? session? cookie?
          +4
          А какая разница, где хранить строку, которая и так при каждом запросе в открытом виде отправляется?
            0

            Одно дело по https отправляется. А другое дело хранится в браузере. Вредоносное расширение может получить ключи?

              +2
              Такое «вредоносное расширение», которое имеет доступ к локальным данным сайта, может просто взять и отправить запрос от вашего имени прямо на месте.
              Если для вас вопрос утечки токена — это большая проблема, то стоит пересмотреть архитектуру, и в частности целесообразность использования JWT вообще.
                0
                1. просто взять, отправить запрос от моего имени, и отправить вместе с токеном — это разные вещи. получается localStorage это никакая не песочница для сайта? нет изоляции вообще? кто угодно может пойти и выдернуть что хочет?

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

                а вполне обычный кейс, человек поставил скомпрометированое расширение, или открыл вредоносный сайт в соседней вкладке.

                получается токены могут быть успешно получены из соседней вкладки?
                  0
                  Как раз наоборот, ВСЁ вами описанное — песочница сайта, прикрытая CORS-политиками. В этом плане они абсолютно равнозначны. Чтобы считать их, нужно в эту песочницу пролезть. А раз пролез, то зачем тащить что-то наружу, если можно пользоваться прямо на месте?
                    0

                    Ну не совсем так.


                    Куки прикрываются именно что CORS-политиками, а их можно обойти если отправлять простой запрос, не выходящий за рамки возможностей HTML формы.


                    А вот localStorage защищена уже Same Origin песочницей, и туда можно пробраться только через XSS.

            +1
            Если можешь себе позволить не полное спа, то куки, иначе без httponly это всё равнозначно.
              +1
              Пожалуй, максимально безопасно будет сделать так: http-only кука, на сервер ходить по https. В самом токене хранить только самые базовые значения (например, только логин пользователя и fingerprint), а уже на сервере решать, что именно этот пользователь может делать. В примере в статье есть поле
              "admin":true
              , это выглядит не слишком безопасно. А идея добавлять случайные строки вполне катит. Ах да, и ограничивать токен по времени, активно использовать revoke/refresh политики. А еще CORS и все такое. Тогда атаки типа man-in-the-middle почти не страшны.
                +1
                В самом токене хранить только самые базовые значения (например, только логин пользователя и fingerprint), а уже на сервере решать, что именно этот пользователь может делать.
                А чем это тогда отличается от обычной сессии?
                  0
                  Вопрос, на который я отвечал подразумевает, что хранение происходит на клиенте, который является браузером. Там разница, действительно, не слишком существенная. За исключением того, что, пожалуй, дополнительное шифрование никогда никому не мешало в плане безопасности. JWT становится действительно оправданным и выигрышным на масштабе, особенно при использовании микросервисов. Там и payload может быть больше и содержать более развернутую информацию, и вот вам единый формат для всех сервисов (в том числе и для браузеров). И вопрос JWT на сервере стоит не так остро.
                    0
                    А чем это тогда отличается от обычной сессии?

                    stateless и прочие плюшки токенов сохраняются.

                    Вот тоже не понимаю, почему логика должна зависеть от того что лежит в токене.
                    По идее там должны быть только «read-only» значения для клиента. На бэкенде мы все равно в большинстве случаев определяем юзера по этому токену и подтягиваем его данные из базы или какого-нибудь сервиса (с ролями/правами и т.д.)
                      +1
                      а бэкенде мы все равно в большинстве случаев определяем юзера по этому токену и подтягиваем его данные из базы или какого-нибудь сервиса (с ролями/правами и т.д.)


                      Представьте, у вас микросервисная архитектура. Один из сервисов предоставляет премиальную услугу (некую информацию, которую могут получить только премиальные пользователи). Этот сервис ничего не знает о пользователях и не имеет связи с базой данных пользователей (зачем?). Вместо этого этот сервис может напрямую из токена взять права или роль пользователя и определить — может ли данный пользователь получать данную информацию. Причём данный сервис может делать запросы еще к каким-то сервисам (которые в свою очередь тоже могут определять, что можно показать, а что нельзя опять же по данным токена). Соответственно, в такой схеме мы избегаем избыточных запросов к базе пользователей. Конечно, вы скажете, запросы можно кэшировать и нагрузка не будет сильно высокой — но вот видите, уже добавляется очередная задача, которой можно было избежать.
                        0
                        Согласен, валидный юзкейс. Но все-таки есть куча микросервисных архитектур где можно не передавать sensitive данные в токене, что не делает само использование jwt бесполезным.
                    0
                    поле «admin»:true
                    ну какие-то клеймы же можно положить в токен, чтобы на фронте отображать/скрывать контент для админа или обычного пользователя и другие кейсы делать.

                    а на бэке все равно проверять права, читать из токена это не секьюрно. даже если они подписаны.
                      +1
                      а на бэке все равно проверять права, читать из токена это не секьюрно. даже если они подписаны.

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

                        понятное дело что по хорошему — при изменении прав/доступов и т.д. надо перевыпускать токен, а старый отправлять в черный список.
                          0
                          Access Token не должен жить долго. Вот Refresh Token — да, но он права не определяет. Старый токен отправить в черный список нельзя, так как тогда надо вести историю вообще всех выданных на данный момент токенов, а это в свою очередь лишает смысла использование JWT.
                            –1
                            Access Token не должен жить долго

                            для вредоносного скрипта даже 10 сек хватит сделать все.

                            Старый токен отправить в черный список нельзя

                            как раз практика говорит о том, что рефреш токен когда перевыпускается — старый прибивается. ибо он не нужен нигде, ни на фронте, ни на беке.

                            а Access Token добавляют в черный список когда делают блокировку токенов, например пользрователь руками в личном кабинете делает выход со всех устройств, кроме текущего, тогда система добавляет все Access Token'ы в черный список.
                              0

                              За эти 10 секунд никто не успеет выдать пользователю права, а потом забрать их.

                                0
                                для вредоносного скрипта даже 10 сек хватит сделать все.

                                Хватит. А хватит ли 10 секунд, чтобы обнаружить компрометацию учетной записи/токена, перенастроить права и заблеклистить токен?

                                Сама идея Access/Refresh токенов как раз и базируется на том, что мы доверяем содержимому подписанного Access-токена и потому он должен быть коротко живущим. А вот Refresh-токен мы используем только для получения нового Access-токена. И ему мы доверяем только в плане аутентификации пользователя (но не авторизации!). И так как для удобства Refresh-токен является долгоживущим, там и нужна возможность блокировки.

                                как раз практика говорит о том, что рефреш токен когда перевыпускается — старый прибивается. ибо он не нужен нигде, ни на фронте, ни на беке.

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

                      0
                      del
                        0
                        в куках с флагами secure и httponly
                          0

                          Лучше не изобретать велосипеды и следовать рекомендациям owasp

                          0
                          Достаточно не плохое обсуждение и решения подглядел тут — gist.github.com/zmts/802dc9c3510d79fd40f9dc38a12bccfc
                            +3

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

                              0
                              Спасибо за ссылку, отличная статья.
                                0

                                Прочитал статьи эти. Один момент смутил — автор говорит, что из localStorage любой javascript может получить данные. Но вроде же можно только с того же домена читать localStorage? Наоборот, если пользователь зайдет на вредоносный сайт, то он сможет какой-нибудь запрос на https://sberbank.ru с правильными куками отправить, а вот ничего из localStorage относящегося к sberbank.ru он использовать не сможет, разве не так?

                                  +1

                                  Вы понимаете все верно. Куки подвержены CSRF, а localStorage подвержен XSS.


                                  Автор статьи подразумевает, что бороться с CSRF значительно проще, потому что он изучен на десяток лет больше, и в принципе закрывается csrf токеном. В то же время XSS значительно сложнее для защиты — к примеру каждый новый SPA Framework может иметь свою дыру.

                                +1
                                Перехват токенов

                                Непонятный пункт.
                                Если предположить что клиент это браузер, то зачем вообще JWT, если можно кукисы использовать?

                                PS: Ни разу не слышал о таком механизме защиты от «перехвата токена» (есть OAuth2.0 Proof-of-Possession, но не похоже что его широко используют в данный момент).
                                  0
                                  Не путайте теплое с мягким. Кукисы — это метод хранения/доставки информации, а JWT — метод ее кодирования (шифрования).
                                    +1
                                    Если у автора это просто «метод кодирования», то в чем вопрос «безопасности метода кодирования»?
                                    И причем тут «перехват» (который как раз угроза доставки\хранения)?

                                    Автор явно замахивался на что-то большее чем рассмотрения «метод кодирования». JWT например является частью эко-системы OpenID Connect, который подразумевает что клиент не всегда браузер, а значит надежный cookie management может быть недоступен.

                                    PS: В session-кукисах JWT использовать безусловно можно (вот Play 2 это делает по умолчанию), вопрос целесообразности только.
                                      +1
                                      Суть JWT не в том, что мы заменяем им id сессии в куках. Суть в том, что в токене есть вся необходимая для выполнения запроса информация о пользователе (user_id, роли, например) и сервис, обрабатывая запрос, не лезет в базу пользователей на каждый чих.

                                      А как передать этот токен с запросом и где хранить на клиенте — каждый решает сам, факторов много (вдруг общение по web-сокетам идёт, какие тогда тут куки).
                                  0
                                  По поводу фингерпринта и перехвата токена через xss: как я понял, подразумевается, что сам токен хранится в каком-то хранилище, которое доступно из js.
                                  Тогда мне не очень очевидно: чем это лучше хранения самого токена в cookie с HttpOnly Secure SameSite?
                                    0

                                    Куки подвержены CSRF-атаке, а потому их надо дополнительно защищать анти-CSRF-токеном. Который, в свою очередь, может быть угнан через XSS.


                                    Таким образом, куки не дают надежной защиты от XSS, они просто увеличивают сложность атаки.

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

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