Как декодировать токен jwt в javascript без использования библиотеки?


210

Как я могу декодировать полезную нагрузку JWT, используя JavaScript? Без библиотеки. Таким образом, токен просто возвращает полезный объект, который может использоваться моим клиентским приложением.

Пример токена: xxxxxxxxx.XXXXXXXX.xxxxxxxx

И результат - полезная нагрузка:

{exp: 10012016 name: john doe, scope:['admin']}

1
Как это было закодировано? Просто сделай наоборот. Вам понадобится общий секрет.
Lucky Soni

Он был закодирован бэкэнд API, который использовал библиотеку php. Здесь мне нужна полезная нагрузка, которая закодирована с использованием base64, я думаю ...
Chrisk8er

1
Вы можете попробовать зайти на сайт jwt.io и получить предоставляемую библиотеку JavaScript.
Квентин

12
Поскольку этот вопрос имеет некоторый трафик, я хочу добавить отказ от ответственности: если вы слепо декодируете полезную нагрузку токена, без проверки подписи, вы можете (или не можете) столкнуться с проблемами безопасности! Убедитесь, что вы понимаете свою архитектуру безопасности, прежде чем вслепую использовать любой код, приведенный в этом вопросе о стековом потоке.
Карстен Хоффманн

5
@CarstenHoffmann А как именно я проверяю подпись ??
Саураб Тивари

Ответы:


469

Рабочая функция парсера текста JWT в Юникоде:

function parseJwt (token) {
    var base64Url = token.split('.')[1];
    var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
    var jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
    }).join(''));

    return JSON.parse(jsonPayload);
};

2
К сожалению, это не похоже на работу с текстом Unicode.
Пол МакМэхон

2
Это решение можно использовать даже в Postman (тестовый набор), поскольку оно не требует установки дополнительных библиотек. Я использовал его для извлечения идентификатора пользователя из токена авторизации.
Wlad

2
ПРИМЕЧАНИЕ: в Почтальоне мне пришлось убрать «окно», JSON.parse(window.atob(base64))чтобы оно заработало. Просто return JSON.parse(atob(base64));и затем postman.setEnvironmentVariable("userId", parseJwt(jsonData.access_token)); «access_token» в моем случае ключ значения маркеров в ответ (может отличаться в вашем случае).
Wlad

12
Приведенное выше решение заменяет только первые «-» и «_» в токене (функция «javascript», которая причиняет мне боль). Просто замените третью строку в ответе:var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
Гоночный головастик

2
jwt-decodeМодуль лучше использовать, потому что он небольшой, но лучше обрабатывает.
Рантьев


47

Вы можете использовать jwt-decode , чтобы потом написать:

import jwt_decode from 'jwt-decode';

var token = 'eyJ0eXAiO.../// jwt token';

var decoded = jwt_decode(token);
console.log(decoded);
/*{exp: 10012016 name: john doe, scope:['admin']}*/

68
«Я имею в виду нет библиотеки».
SherloxTV

У них проблемы с этой библиотекой. В основном с использованием Firefox. Проблема, с которой я столкнулся, заключалась в том, что если токен == null в результате выхода из системы или истечения срока действия; что это просто убивает страницу с ошибкой.
LUser

1
@ApertureSecurity вам нужно поймать эту ошибку, но по общему признанию именно поэтому я не хочу использовать эту библиотеку
Люк Робертсон

Это не похоже на поддержку GZIP. На самом деле, я не могу найти библиотеки JS, которые поддерживают GZIP для заявлений.
Эндрю Т Финнелл

18

Вы можете использовать чистую atob()функцию JavaScript для декодирования токена в строку:

atob(token.split('.')[1]);

или непосредственно разобрать его в объект json:

JSON.parse(atob(token.split('.')[1]));

читать о atob()и btoa()встроенной в JavaScript функции Base64 кодирование и декодирование - веб - АФИ | MDN .


9

@Peheje будет работать, но у вас будут проблемы с юникодом. Чтобы исправить это, я использую код на https://stackoverflow.com/a/30106551/5277071 ;

let b64DecodeUnicode = str =>
  decodeURIComponent(
    Array.prototype.map.call(atob(str), c =>
      '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
    ).join(''))

let parseJwt = token =>
  JSON.parse(
    b64DecodeUnicode(
      token.split('.')[1].replace('-', '+').replace('_', '/')
    )
  )


let form = document.getElementById("form")
form.addEventListener("submit", (e) => {
   form.out.value = JSON.stringify(
      parseJwt(form.jwt.value)
   )
   e.preventDefault();
})
textarea{width:300px; height:60px; display:block}
<form id="form" action="parse">
  <textarea name="jwt">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkrDtGhuIETDs8OoIiwiYWRtaW4iOnRydWV9.469tBeJmYLERjlKi9u6gylb-2NsjHLC_6kZNdtoOGsA</textarea>
  <textarea name="out"></textarea>
  <input type="submit" value="parse" />
</form>


+1, но если комментарий Racing Tadpole относительно ответа Peheje правильный (то, что вызовы замены будут заменять только первый экземпляр), то то же самое исправление применимо и здесь.
Гэри МакГилл

9

Поскольку объект "window" отсутствует в среде nodejs, мы можем использовать следующие строки кода:

let base64Url = token.split('.')[1]; // token you get
let base64 = base64Url.replace('-', '+').replace('_', '/');
let decodedData = JSON.parse(Buffer.from(base64, 'base64').toString('binary'));

У меня это отлично работает. Надеюсь, поможет.


1
идеальный ответ для узла js
ирешан патирана

7
function parseJwt(token) {
  var base64Payload = token.split('.')[1];
  var payload = Buffer.from(base64Payload, 'base64');
  return JSON.parse(payload.tostring());
}
let payload= parseJwt("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c");
console.log("payload:- ", payload);

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

npm install buffer
var Buffer = require('buffer/').Buffer

6

Я использую эту функцию, чтобы получить полезную нагрузку, заголовок, exp (время истечения), iat (выдано в) на основе этого ответа

function parseJwt(token) {
  try {
    // Get Token Header
    const base64HeaderUrl = token.split('.')[0];
    const base64Header = base64HeaderUrl.replace('-', '+').replace('_', '/');
    const headerData = JSON.parse(window.atob(base64Header));

    // Get Token payload and date's
    const base64Url = token.split('.')[1];
    const base64 = base64Url.replace('-', '+').replace('_', '/');
    const dataJWT = JSON.parse(window.atob(base64));
    dataJWT.header = headerData;

// TODO: add expiration at check ...


    return dataJWT;
  } catch (err) {
    return false;
  }
}

const jwtDecoded = parseJwt('YOUR_TOKEN') ;
if(jwtDecoded)
{
    console.log(jwtDecoded)
}

Этот ответ несколько лучше, но у него есть два с половиной вопроса. Во-первых, он не проверяет подпись (элемент массива 2). Во-вторых, REPLACE не будут работать правильно, потому что они пропускают флаг "g" на регулярном выражении (будут заменять только первые вхождения - и _ на JWT, как Racing Tadpole прокомментировал другой пост). И половина: чтобы декодировать элементы массива 0 и 1, вы могли бы использовать цикл FOR вместо дублирования всего кода (это короткий код, но его можно сделать более эффективным, так как SPLIT выполняется дважды). ).
Cyberknight

4

все функции jwt.io не поддерживают все языки. В NodeJs вы можете использовать

var decoded = jwt.decode(token);

1
Без библиотеки вы просто выполняете декодирование base64 во второй части токена {var payload = token.split ('.') [1]); } Затем выполните декодирование base64 {var decodedData = atob (payload); }
Джитин Виджаян,

4

Я нашел этот код в jwt.io, и он работает хорошо.

//this is used to parse base64
function url_base64_decode(str) {
  var output = str.replace(/-/g, '+').replace(/_/g, '/');
  switch (output.length % 4) {
    case 0:
      break;
    case 2:
      output += '==';
      break;
    case 3:
      output += '=';
      break;
    default:
      throw 'Illegal base64url string!';
  }
  var result = window.atob(output); //polifyll https://github.com/davidchambers/Base64.js
  try{
    return decodeURIComponent(escape(result));
  } catch (err) {
    return result;
  }
}

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

Я надеюсь, что это поможет вам.


2

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

Кроме того, мне потребовалось несколько минут, чтобы понять, что токен - это полный набор учетных данных, которые отправляются обратно (весь токен JWT, а не только его часть idToken). Просто, как только вы это знаете ..

import jwt_decode from 'jwt-decode';

var token = 'eyJ0eXAiO.../// jwt token';
var decoded = jwt_decode(token);

/*{exp: 10012016 name: john doe, scope:['admin']}*/


2
Публиковать точно такой же ответ как другой пользователь, который также идет вразрез с тем, о чем просил OP, не очень полезно
Cacoon

2

Простое решение NodeJS для декодирования веб-токена JSON (JWT)

function decodeTokenComponent(value) {
    const buff = new Buffer(value, 'base64')
    const text = buff.toString('ascii')
    return JSON.parse(text)
}

const token = 'xxxxxxxxx.XXXXXXXX.xxxxxxxx'
const [headerEncoded, payloadEncoded, signature] = token.split('.')
const [header, payload] = [headerEncoded, payloadEncoded].map(decodeTokenComponent)

console.log(`header: ${header}`)
console.log(`payload: ${payload}`)
console.log(`signature: ${signature}`)

2

Ответ основан на GitHub - auth0 / jwt-decode . Изменен ввод / вывод для включения разделения строки и возврата объекта {заголовок, полезная нагрузка, подпись}, так что вы можете просто передать весь токен.

var jwtDecode = function (jwt) {

        function b64DecodeUnicode(str) {
            return decodeURIComponent(atob(str).replace(/(.)/g, function (m, p) {
                var code = p.charCodeAt(0).toString(16).toUpperCase();
                if (code.length < 2) {
                    code = '0' + code;
                }
                return '%' + code;
            }));
        }

        function decode(str) {
            var output = str.replace(/-/g, "+").replace(/_/g, "/");
            switch (output.length % 4) {
                case 0:
                    break;
                case 2:
                    output += "==";
                    break;
                case 3:
                    output += "=";
                    break;
                default:
                    throw "Illegal base64url string!";
            }

            try {
                return b64DecodeUnicode(output);
            } catch (err) {
                return atob(output);
            }
        }

        var jwtArray = jwt.split('.');

        return {
            header: decode(jwtArray[0]),
            payload: decode(jwtArray[1]),
            signature: decode(jwtArray[2])
        };

    };

1

Вот более многофункциональное решение, которое я только что сделал после изучения этого вопроса:

const parseJwt = (token) => {
    try {
        if (!token) {
            throw new Error('parseJwt# Token is required.');
        }

        const base64Payload = token.split('.')[1];
        let payload = new Uint8Array();

        try {
            payload = Buffer.from(base64Payload, 'base64');
        } catch (err) {
            throw new Error(`parseJwt# Malformed token: ${err}`);
        }

        return {
            decodedToken: JSON.parse(payload),
        };
    } catch (err) {
        console.log(`Bonus logging: ${err}`);

        return {
            error: 'Unable to decode token.',
        };
    }
};

Вот несколько примеров использования:

const unhappy_path1 = parseJwt('sk4u7vgbis4ewku7gvtybrose4ui7gvtmalformedtoken');
console.log('unhappy_path1', unhappy_path1);

const unhappy_path2 = parseJwt('sk4u7vgbis4ewku7gvtybrose4ui7gvt.malformedtoken');
console.log('unhappy_path2', unhappy_path2);

const unhappy_path3 = parseJwt();
console.log('unhappy_path3', unhappy_path3);

const { error, decodedToken } = parseJwt('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c');
if (!decodedToken.exp) {
    console.log('almost_happy_path: token has illegal claims (missing expires_at timestamp)', decodedToken);
    // note: exp, iat, iss, jti, nbf, prv, sub
}

Я не смог сделать это работоспособным в инструменте фрагмента кода StackOverflow, но вот примерно то, что вы увидели бы, запустив этот код:

введите описание изображения здесь

Я заставил parseJwtфункцию всегда возвращать объект (в некоторой степени по причинам статической типизации).

Это позволяет вам использовать синтаксис, такой как:

const { decodedToken, error } = parseJwt(token);

Затем вы можете протестировать во время выполнения определенные типы ошибок и избежать любых конфликтов имен.

Если кто-то может подумать о каких-либо небольших усилиях, внесении значительных изменений в этот код, смело редактируйте мой ответ в интересах next(person).


0

На основании ответов здесь и здесь :

const dashRE = /-/g;
const lodashRE = /_/g;

module.exports = function jwtDecode(tokenStr) {
  const base64Url = tokenStr.split('.')[1];
  if (base64Url === undefined) return null;
  const base64 = base64Url.replace(dashRE, '+').replace(lodashRE, '/');
  const jsonStr = Buffer.from(base64, 'base64').toString();
  return JSON.parse(jsonStr);
};

-1

Запустив Javascript node.js express, я должен был сначала установить пакет следующим образом:

npm install jwt-decode --save

тогда в моем коде app.js получите пакет:

const jwt_decode = require('jwt-decode');

Затем запустите код:

let jwt_decoded = jwt_decode(jwt_source);

Тогда магия:

console.log('sub:',jwt_decoded.sub);

4
помните, «без использования библиотеки»
Олаф

1
хорошо, достаточно справедливо. Однако я столкнулся с той же проблемой, и у меня не было ограничений на невозможность использования библиотеки. Это сработало для меня. Я оставляю это сообщение, потому что, возможно, кто-то еще сталкивается с подобной проблемой и не имеет такого же ограничения.
Дэвид Уайт
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.