Як використовувати JSON Web Tokens (JWT) для автентифікації

9 хв. читання

Навіщо?

JSON веб-токен (JWT) призначений для передачі підписаних «заявок» (claims) між службами (як зовнішніми, так і внутрішніми для вашого застосунку/сайту). «Заявки» — частина інформації, яку інші можуть переглядати та/або перевіряти, але не змінювати.

Що?

Веб-токен JSON (JWT) — компактний та безпечний спосіб представлення «заявок» (claims) для передачі між двома сторонами. Заявки у JWT являють собою JSON-об'єкт, підписаний за допомогою JSON Web Signature (JWS).

IETF

Простими словами

Щоб ідентифікувати/здійснити автентифікацію користувача у вашому (веб/мобільному) застосунку, розмістіть стандартизований токен у заголовок або url сторінки (або кінцеву точку API). Таким чином ми засвідчуємо, що користувач авторизувався та може переглядати бажаний контент.

Приклад:

https://www.yoursite.com/private-content/?token=eyJ0eXAiOiJKV1Qi.eyJrZXkiOi.eUiabuiKv

Як виглядає JWT?

Токени виглядають як рядки з «URL-безпечних» символів, що кодують інформацію. Вони складаються з трьох складових, розділених крапками (для читабельності розміщуємо на окремих рядках).

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9           // header (заголовок)
.eyJrZXkiOiJ2YWwiLCJpYXQiOjE0MjI2MDU0NDV9      // payload (корисне навантаження)
.eUiabuiKv-8PYk2AkGY4Fb5KMZeorYBLw261JPQD5lM   // signature (підпис)

1. Заголовок (Header)

Перша частина JWT — рядок, що закодовує звичайний JavaScript об'єкт, який описує токен, а також використаний алгоритм хешування.

2. Корисне навантаження (Payload)

Друга частина JWT формує основу токена. Довжина корисного навантаження пропорційна кількості даних, збережених у JWT. Загальне правило: зберігати мінімум у JWT.

3. Підпис (Signature)

Третя і кінцева частина JWT — підпис, згенерований на основі заголовка та корисного навантаження. Використовується для перевірки JWT.

Що таке «заявки» (claims) ?

Заявки — зумовлені ключі та їх значення:

  • iss — постачальник токена;
  • exp — дата закінчення терміну дії (якщо термін дії вийшов — токени будуть відхилені). Помітьте: за специфікацією зазначається в секундах;
  • iat — час створення токена. Можна використовувати для визначення віку JWT;
  • nbf— «not before» (не раніше). Зазначення моменту активації токена у майбутньому;
  • jti — унікальний ідентифікатор JWT. Потрібен, щоб запобігти повторному використанню JWT.
  • sub — описуваний об'єкт (рідко використовується);
  • aud — одержувачі (рідко використовується);

Більше інформації за посиланням.

Приклад

Розглянемо простий приклад (повний сирцевий код можна переглянути за посиланням).

Сервер

Використовуючи основний node.js HTTP-сервер, ми створюємо 4 кінцеві точки у /example/server.js:

  1. /home: домашня сторінка (необов'язково, але тут наша форма входу);
  2. /auth: здійснюється автентифікація гостя (повертає помилку та форму входу у разі невдачі);
  3. /private : наш обмежений контент — потрібен вхід (дійсний токен сесії) для перегляду;
  4. /logout: анулює токен та здійснює вихід користувача (перешкоджає повторному використанню старого токена).

Ми навмисно зробили server.js якомога простішим для:

  • Читабельності;
  • Підтримуваності;
  • Придатності для тестування (хелпери/обробники тестуються окремо).

Хелпери

Усі хелпери зберігаються у файлі /example/lib/helpers.js. Два найцікавіші/найактуальніші методи у спрощеній версії представлені тут:

// генерація JWT
function generateToken(req){
  return jwt.sign({
    auth:  'magic',
    agent: req.headers['user-agent'],
    exp:   Math.floor(new Date().getTime()/1000) + 7*24*60*60; // Зверніть увагу: у секундах!
  }, secret);  // secret визначено у змінній середовища JWT_SECRET
}

Метод генерує наш JWT-токен, коли користувач здійснює автентифікацію (потім він відправляється назад клієнту у заголовку авторизації для використання у наступних запитах).

Наступний метод:

// перевіряє токен, зазначений у заголовку запиту
function validate(req, res) {
  var token = req.headers.authorization;
  try {
    var decoded = jwt.verify(token, secret);
  } catch (e) {
    return authFail(res);
  }
  if(!decoded || decoded.auth !== 'magic') {
    return authFail(res);
  } else {
    return privado(res, token);
  }
}

Метод перевіряє JWT, наданий клієнтом. Якщо перевірку пройдено, користувач побачить прихований вміст сторінки, в іншому разі — сторінку authFail.

Помітьте: Так, обидва методи синхронні. Враховуючи те, що вони не містять введення/виведення інформації чи мережевих запитів, досить безпечно виконувати їх одночасно.

Тести

Тести для серверних маршрутів та хелперів знаходяться за посиланням.

  1. /example/test/functional.js — виконує усі хелпери, створені у /example/lib/helpers.js ;
  2. /example/test/integration.js — імітує запити, які користувач відправляє на сервер, і перевіряє відповіді.

Помітьте: ми створили базовий «макет» http req/res об'єктів.

Часті питання (FAQ)

Якщо я розміщую JWT в URL-адресі або заголовку, це безпечно?

Гарне питання! Швидка відповідь: ні. Якщо ви не використовуєте SSL/TLS (https в url-адресі) для шифрування з'єднання, відправка токена у незашифрованому вигляді завжди буде небезпечною (токен може бути перехоплений та використаний повторно зловмисником). Марно намагатися пом'якшити наслідки, додавши перевірювані «заявки» до токена. Наприклад, перевіряти чи прийшов запит з того ж браузера (user-agent), IP-адреси, або перевіряти відбитки браузера. (Роз'яснюємо щодо відбитків).

Рішенням буде одне з тверджень:

  • використовувати одноразові токени (термін дії яких спливає після кліку на посилання);
  • не використовувати url-токени там, де потрібен високий ступінь безпеки (наприклад, не надсилайте будь-кому посилання, що дозволяє йому здійснити транзакцію).

Використовувати JWT токени в url можна для:

  • верифікації акаунта, коли ви відправляєте на пошту посилання після реєстрації на вашому сайті: https://yoursite.co/account/verify?token=jwt.goes.here;
  • зміни пароля, щоб упевнитись, що користувач, який здійснює зміну, має доступ до пошти, зв'язаної з акаунтом: https://yoursite.co/account/reset-password?token=jwt.goes.here.

В обох випадках доречними будуть одноразові токени.

Як ми перериваємо сесії?

Уявіть, що у користувача вашого застосунку вкрали гаджет. Як ви зробите недійсним токен, який він використовував?

Суть JWT у тому, що токени не мають стану. Вони можуть бути обчислені будь-яким вузлом у кластері та перевірені без (повільного) запиту до бази даних.

Зберігати токен у базі даних?

LevelDB

Якщо ваш застосунок невеликий або ви не хочете запускати сервер Redis, ви можете отримати більше переваг від нього, використовуючи LevelDB.

Ми можемо зберігати у базі даних як дійсні, так і недійсні токени. Так ми вимушені звертатися два рази до бази даних, щоб перевірити токени. Краще зберігати усі токени та встановлювати їх «допустимість» як true або false:

Приклад запису, збереженого у LevelDB:

"GUID" : {
  "auth" : "true",
  "created" : "timestamp",
  "uid" : "1234"
}

Ми будемо шукати цей запис за його GUID:

var db = require('level');
db.get(GUID, function(err, record){
  // псевдокод
  if(record.auth){
    // прихований контент
  } else {
    // повідомлення про помилку
  }
});

Детальніше за посиланням.

Redis

Redis — масштабований спосіб зберігати токени. Якщо ви повний новачок, ознайомтесь з наступним матеріалом:

Redis масштабується (за умови, що у вас є ОЗУ).

Почніть роботу з Redis вже сьогодні!

Кешування? — Швидка відповідь: використовуйте Redis.

Повернення відвідувача (без збереження стану між сеансами)

Cookie зберігаються на стороні клієнта та надсилаються браузером на сервер за кожним запитом. Якщо користувач закриває браузер, cookie зберігаються, тому він може продовжити роботу з того місця, на якому зупинився, без повторного входу в систему. Однак файли cookie будуть відправлятися на кожен запит, що відповідає шляху та домену відправника, у тому числі для зображень та CSS, де це непотрібно.

localStorage забезпечує кращий механізм для зберігання токенів під час та між сеансами браузерів.

Браузерні застосунки

Існує два варіанти зберігати ваші JWT:

  1. Використовувати localStorage для зберігання JWT на боці клієнта (не забудьте відправити JWT в заголовку авторизації для наступних запитів ajax/http);
  2. Зберігати JWT у cookie (встановити й забути).

Очевидно, ми підтримуємо підхід без cookie. Але за правильного використання, cookie досі прийнятні у сучасному вебі.

Корисні посилання:

Програмний (API) доступ

Інші служби, отримуючи доступ до вашого API, повинні зберігати токен у системі (наприклад, Redis або SQLite для мобільних застосунків) та надсилати його з кожним запитом.

Як згенерувати секретний ключ?

Оскільки JSON Web Tokens (JWT) не повинні підписуватись за допомогою асиметричного шифрування, вам не потрібно генерувати секретний ключ за допомогою ssh-keygen. Ви з легкістю можете використовувати надійний пароль, наприклад: https://www.grc.com/passwords.htm за умови, що він довгий і випадковий. Імовірність колізії (хтось може змінити корисне навантаження, додати чи змінити «заявки» та створити дійсний підпис) досить низька. І якщо ви з'єднаєте два з цих надійних паролів (рядків), отримаєте 128-бітний рядок ASCII. Тому ймовірність колізії менша за кількість атомів у Всесвіті.

Щоб легко та швидко створити секретний ключ за допомогою крипто-бібліотеки Node.js, виконайте наступну команду:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'));"

Ви можете використовувати ключ RSA, але це не обов'язково.

Головне, що потрібно пам'ятати: не діліться ключем з людьми, які не у вашій команді та не публікуйте його на GitHub!

Який Node.js модуль?

Пошук «JSON Web Token» на NPM видає багато результатів.

Як використовувати JSON Web Tokens (JWT) для автентифікації

Загальне використання в інших Node.js проектах?

Рекомендуємо використовувати модуль jsonwebtoken, створений експертами з автентифікації auth0, у поєднанні з node-jws .

Ще один варіант за посиланням, створений joaquimserafim.

Важлива інформація

Додатково ознайомтесь

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.8K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація