Предположим, вы владелец веб-сайта в 2014 году. Ваш сайт очень популярен, на нём зарегистрировано несколько тысяч человек. Но как ваш сайт понимает какой именно пользователь авторизован в данный момент?

Возможно, вы храните информацию об авторизованном пользователе в PHP сессии. Либо же вы пошли немного дальше, и храните эту информацию в зашифрованной куке. Если же вы любите всё новое и неизведанное, то наверняка уже знаете о существовании объединения JOSE – JSON Object Signing and Encryption group, целью которой c 2011 года, как раз и является стандартизация метода безопасной идентификации пользователя. Другими словами, участники этой группы как раз и пытались придумать единый и безопасный способ идентификации. Если что, идентификация – это процедура, после которой вы чётко понимаете какой конкретный пользователь перед вами. Вернее перед вашим веб-сайтом.

И, как вы догадываетесь из заголовка подкаста, результатом работы JOSE и был стандарт JWT. А если в 2014 вы ещё и достаточно терпеливы или ленивы, то буквально в следующем году группа IETF официально и стандартизирует JWT.

И сегодня я и расскажу вам про этот формат токенов доступа; про то, как он устроен; и про то, почему он лучше чем хранение сессий на сервере.

Из чего состоит JWT?

JWT токен состоит из трёх частей, разделённых точкой: заголовкаполезных данных или payload‘а и подписи. Каждая из частей кодируется форматом base64.

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

Вторая часть токена или “полезные данные” содержит так называемые JWT claims. На русский это переводится как “заявки”, но для понимания я бы назвал их “полями” или “набором полей”.

Таких наборов существует три вида:

  • Зарегистрированные или стандартные: это поля, в которых можно хранить, например, следующее:
    • Имя субъекта, который выпустил токен
    • Время, когда токен перестанет быть валидным
    • Когда токен был выпущен, ну и так далее. Хоть эти поля и стандартизированы, но ни одно из них не является обязательным. Идём дальше
  • Существует публичный набор полей, таких как;
    • Полное имя пользователя
    • Пол
    • Ссылка на аватарку и так далее. Такой набор нужен для того, чтобы не ходить лишний раз в базу за информацией и экономить ресурсы
  • И приватный набор полей для вашего приложения. Здесь можно указать любые данные на вкус и цвет вашего приложения, и они будут специфичными для него.

С заголовкой и полезными данными разобрались, и осталось самое главное, что и делает токен защищённым: подпись. Формула JWT подписи состоит из следующих компонентов:

  • Из заголовка токена (header) или его первой части;
  • Из полезного содержимого (payload) или второй части;
  • И из секретного ключа (secret), который знает только ваш сервер.

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

Как отправлять JWT токен на сервер?

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

  • Через куки
  • Либо через HTTP заголовок Authorization с префиксом Bearer. Правда, в этом случае стоит придерживаться лимита в 8 КБ. Если вдруг вы привысили этот лимит, то стоит попробовать сократить количество данных во второй части токена (payload).

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

А как же приложению понять, что пришедший токен валидный? Очень просто – сформировать подпись на основе секретного ключа, первой (header) и второй (payload) части. Если подпись совпадёт с пришедшей, то всё хорошо, можно пускать пользователя дальше.

Почему JWT токен лучше, чем сессии?

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

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

И казалось бы: проблему можно решить, если положить в payload некий идентификатор, который при каждом обращении можно сверять на сервере со списком активных токенов. Но тогда мы лишаемся важного достоинства JWT токенов – отсутствие необходимости ходить в базу.
Хотя, можно хранить этот некий идентификатор, например, в Redis. Тогда получается вполне неплохо. Но важно тут помнить, что, конечно, весь токен в базе хранить ни в коем случае нельзя, потому что небезопасно. Поэтому я и сказал “идентификатор” – некая последовательность символов внутри полезных данных. А вообще, можно сделать ещё лучше!

Существует такое решение, как пара access и refresh токенов. В ней access токен живёт совсем немного – всего несколько минут, а refresh используется для обновления этой пары. Таким образом компроментация access токена позволит радоваться злоумышленнику совсем недолго, потому что совсем скоро access токен протухнет, а без refresh новую пару получить не удастся.

Как работать с JWT токенами на Go?

В этой задаче вам поможет библиотека, угадайте, с каким названием? Да, просто jwt.

У библиотеки удобный API и куча достоинств, загибайте пальцы:

  • поддерживает многопоточное использование;
  • не использует какие-либо зависимости ;
  • в-третьих её код покрыт тестами;
  • и самое главное то, что библиотека аж с 584 звёздочками, под MIT лицензией и не заброшена. Последний коммит был 8 октябряКажется, тут будет актуальна шутка, что библиотека с прошлого года не обновлялась.

Как обычно, спасибо, что слушаете/смотрите/читаете. Услышимся через неделю!