Что такое JWT?
JWT или JSON Web Token представляет собой надежный и безопасный метод передачи информации между сторонами в виде JSON-объектов. Не просто так этот метод один из наиболее популярных в мире.
JWT (далее токен) состоит из трех частей, закодированных в формате Base64. Эти части разделены между собой точками.
header
Первая часть токена является заголовком. Содержит в себе алгоритм шифрования подписи (наш signature) и тип токена (JWT/JWE/JWS). В большинстве случаев при использовании библиотек или сервисов по генерации токенов заголовок выставляется автоматически.
Для ручного варианта напишем функцию createBase64String(), которая будет принимать в себя javascript-объект и вернет готовую строку в формате base64:
const myHeader = { alg: 'HS256', typ: 'JWT', } const createBase64String = (object) => { const jsonObject = JSON.stringify(object); return btoa(jsonObject) } const header = createBase64String(myHeader); console.log(header) // 'eyJhbGciOiJoczI1NiIsInR5cCI6IkpXVCJ9'
2) Преобразовываем его в JSON-строку.
3) Встроенной в JS функцией btoa() кодируем в base64.
4) Теперь header содержит в себе строку 'eyJhbGciOiJoczI1NiIsInR5cCI6IkpXVCJ9'.
payload
Вторая часть содержит в себе пользовательские данные – любые, какие захотим. Однако нужно иметь ввиду, что включать сюда sensitive-информацию (например, пароли в явном виде, ключи, хэши паролей и т.д.) не стоит. Сюда мы можем поместить информацию о времени создания токена (iat), даты окончания (exp), id пользователя и тд. Другими словами, любую информацию, владение которой не сильно поможет хакеру, ну а нам даст своеобразную маркировку на конкретного пользователя.
Пример из моего проекта: iat, exp – указываются в формате UNIX-времени. То есть количестве секунд, прошедших с начала «UNIX-эпохи» (1 января 1970 г. 00ч 00м 00с по UTC):
const myPayload = { nickname: "userNickname", deviceId: "userDeviceId", iat: 9999999, exp: 9999999, } const payload = createBase64String(myPayload); console.log(payload) //"eyJuaWNrbmFtZSI6InVzZXJOaWNrTmFtZSIsImRldmljZUlkIjoidXNlckRldmljZUlkIiwiaWF0Ijo5OTk5OTk5LCJleHAiOjk5OTk5OTl9"
2) Пользуемся ранее созданной функцией и передаем туда наш новый объект.
3) Полученная строка лежит в переменной payload.
signature
Пожалуй, самая важная и ответственная часть JWT – это подпись. Подпись является строкой, которая генерируется в результате хэширования строк header и payload, разделенных точкой, и последующей кодировкой хэша в base64 (стало сложновато, давайте пошагово).
Нам нужно сперва создать подпись HMAC-SHA256 (далее просто хмак) из следующей строки и суперсекретного ключа:
const superKey = 'Этот секретный ключ нельзя никому показывать и передавать!' const myString = header + '.' + payload; // eyJhbGciOiJoczI1NiIsInR5cCI6IkpXVCJ9.eyJuaWNrbmFtZSI6InVzZXJOaWNrTmFtZSIsImRldmljZUlkIjoidXNlckRldmljZUlkIiwiaWF0Ijo5OTk5OTk5LCJleHAiOjk5OTk5OTl9
Так как JS не имеет встроенных функций для создания подписи хмак, необходимо воспользоваться какой-нибудь сторонней библиотекой (например, CryptoJS). Но мы такими самодурствами заниматься не будем и просто предположим, что получили нужную подпись. Ведь вышенаписанная информация нужна для понимания всего процесса создания токена и общего развития, на практике же мы будем пользоваться готовой библиотекой jsonwebtoken.
После получения подписи необходимо и ее закодировать в base64:
const sign = // Тут мы получили подпись и преобразовали ее в строку const signature = btoa(sign)
Таким образом в signature будет лежать строка (например: LxTQVx8JgzSBPyG0pSf7Z9Ojo4N7442loY0k1POK1v4), расшифровать которую без суперсекретного ключа нельзя, либо очень-очень сложно (почти невозможно).
Объединив все три части точками, мы получим наш токен:
eyJhbGciOiJoczI1NiIsInR5cCI6IkpXVCJ9.eyJuaWNrbmFtZSI6InVzZXJOaWNrTmFtZSIsImRldmljZUlkIjoidXNlckRldmljZUlkIiwiaWF0Ijo5OTk5OTk5LCJleHAiOjk5OTk5OTl9.LxTQVx8JgzSBPyG0pSf7Z9Ojo4N7442loY0k1POK1v4
«Чота сложна, мож попроще как-нить?»
Да, можно намного проще. Для этого нам понадобится библиотека с открытым исходным кодом. В нашем случае это jsonwebtoken:
Установим через npm:
npm i jsonwebtoken
Далее просто импортируем и воспользуемся методом .sign(payload, secretKey, options):
import jwt from 'jsonwebtoken'; const payload = { id: 12, name: 'Alesha', city: 'Moscow', } const secretKey = 'EzPzLmnSqzy'; const options = { expiresIn: '10m' } const token = jwt.sign(payload, secretKey, options)
1. Мы определили нашу полезную нагрузку (payload). Разумеется, мы не включаем сюда никаких чувствительных данных. Имя к таковым не относится (наверное...). iat и exp библиотека вставляет автоматически, так что явно их указывать не нужно.
2. Мы придумали секретный ключ. Он довольно простой, но на боевом сервере лучше определить что-то более надежное =)
3. В options передаются опции, которые можно найти в официальной документации к библиотеке. Мы выбрали только expiresIn, который определяет срок годности токена (тот самый exp).
В результате в нашей переменной токен будет лежать наш свеженький JWT со сроком действия 10 минут.
Проверить токен можно с помощью метода .verify(token, secretKey, callback(err, decoded)), о чем я раскажу в следующей части статьи.
Итоги
- Пользуемся библиотеками с открытым кодом, чтобы быть уверенными, что наши данные никуда не утекают.
- Не тратим время на самодеятельность по собственноручному созданию токенов (время разработчика дорогое).
- Лайкаем, подписываемся и ждем следующих моих постов.
Комментарии