Jak zdekodować token JWT w javascript bez korzystania z biblioteki?

209

Jak mogę zdekodować ładunek JWT przy użyciu JavaScript? Bez biblioteki. Token po prostu zwraca obiekt ładunku, który może zostać wykorzystany przez moją aplikację front-end.

Przykładowy token: xxxxxxxxx.XXXXXXXX.xxxxxxxx

Rezultatem jest ładowność:

{exp: 10012016 name: john doe, scope:['admin']}
Chrisk8er
źródło
1
Jak to było zakodowane? Po prostu odwróć. Potrzebny będzie wspólny sekret.
Lucky Soni,
Został zakodowany przez interfejs API, który korzystał z biblioteki php. Tutaj potrzebuję ładunku, który został zakodowany przy użyciu base64 i chyba ...
Chrisk8er
1
Możesz spróbować wejść na stronę jwt.io i uzyskać dostępną bibliotekę JavaScript.
Quentin,
12
Ponieważ to pytanie ma pewien ruch, chcę dodać zastrzeżenie: Jeśli na oślep zdekodujesz ładunek tokena, bez sprawdzania poprawności podpisu, możesz (lub nie) napotkać problemy bezpieczeństwa! Upewnij się, że rozumiesz swoją architekturę zabezpieczeń, zanim na ślepo użyjesz kodu podanego w tym pytaniu dotyczącym przepełnienia stosu.
Carsten Hoffmann
4
@CarstenHoffmann A jak dokładnie sprawdzać poprawność podpisu?
Saurabh Tiwari

Odpowiedzi:

468

Pracująca funkcja parsera JWT tekstu Unicode:

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);
};
Peheje
źródło
2
Niestety nie działa to z tekstem Unicode.
Paul McMahon,
2
To rozwiązanie może być nawet używane w Listonoszu (testowanie dotknij), ponieważ nie wymaga instalacji dodatkowej biblioteki. Użyłem go do wyodrębnienia identyfikatora użytkownika z tokena uwierzytelnienia.
Wlad
2
UWAGA: W Listonoszu musiałem usunąć „okno”, JSON.parse(window.atob(base64))aby działało. Właśnie return JSON.parse(atob(base64));wtedy postman.setEnvironmentVariable("userId", parseJwt(jsonData.access_token)); „access_token” jest w moim przypadku kluczem wartości tokena w odpowiedzi (może różnić się w twoim przypadku).
Wlad
12
Powyższe rozwiązanie zastępuje tylko pierwsze „-” i „_” w tokenie („funkcja javascript”, która ciągle powoduje mi ból). Po prostu zamień trzecią linię w odpowiedzi na:var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
Racing Tadpole
2
Lepiej jest użyć jwt-decodemodułu, ponieważ jest mały, ale ma nieco lepszą obsługę.
Rantiev
64

Prosta funkcja z try-catch

const parseJwt = (token) => {
  try {
    return JSON.parse(atob(token.split('.')[1]));
  } catch (e) {
    return null;
  }
};

Dzięki!

Rajan Maharjan
źródło
ładne, zwięzłe i wykorzystuje wszystkie natywne metody!
Chris Love
2
atobzna problemy z Unicode
Tamer Shlash
47

Możesz użyć jwt-decode , aby następnie napisać:

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']}*/
Chłopak
źródło
67
„Mam na myśli brak biblioteki”.
SherloxTV 24.04.17
Są problemy z tą biblioteką. Głównie z użyciem Firefoxa. Problem, na który natrafiłem, polegał na tym, że token == null wynikający z wylogowania lub wygaśnięcia; że to po prostu zabija stronę z błędem.
LUser
1
@ApertureSecurity musisz złapać ten błąd, ale wprawdzie dlatego nie chcę korzystać z tej biblioteki
Luke Robertson
To nie wydaje się wspierać GZIP. W rzeczywistości nie mogę znaleźć bibliotek JS, które obsługują GZIP dla oświadczeń.
Andrew T Finnell,
18

możesz użyć atob()funkcji czystego javascript do zdekodowania tokena na ciąg:

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

lub parsuj bezpośrednio do obiektu json:

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

przeczytać atob()i btoa()wbudowanej funkcji JavaScript Base64 kodowania i dekodowania - Web API | MDN .

Muhammed Moussa
źródło
9

@Peheje będzie działać, ale będziesz mieć problem z Unicode. Aby to naprawić, używam kodu na 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>

Rafael Quintela
źródło
+1, ale jeśli komentarz Racing Tadpole do odpowiedzi Peheje jest poprawny (że wywołania zamiany zastąpią tylko pierwszą instancję), to ta sama poprawka miałaby zastosowanie tutaj.
Gary McGill
9

Ponieważ obiekt „window” nie jest obecny w środowisku nodejs, możemy użyć następujących wierszy kodu:

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

Działa dla mnie idealnie. Mam nadzieję, że to pomoże.

Avik
źródło
1
idealna odpowiedź dla węzła js
ireshan pathirana
7
function parseJwt(token) {
  var base64Payload = token.split('.')[1];
  var payload = Buffer.from(base64Payload, 'base64');
  return JSON.parse(payload);
}
let payload= parseJwt("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c");
console.log("payload:- ", payload);

Jeśli używasz węzła, może być konieczne użycie pakietu bufora:

npm install buffer
var Buffer = require('buffer/').Buffer
hashinclude72
źródło
6

Korzystam z tej funkcji, aby uzyskać ładunek, nagłówek, exp (Expiration Time), iat (Issued At) na podstawie tej odpowiedzi

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)
}
Softmixt
źródło
Ta odpowiedź jest nieco lepsza, ale ma dwa i pół problemu. Po pierwsze, nie sprawdza podpisu (element tablicy 2). Po drugie, REPLACEs nie będą działały poprawnie, ponieważ brakuje im flagi „g” w wyrażeniu regularnym (zastąpią tylko pierwsze wystąpienia - i _ na JWT, jak Racing Tadpole skomentował inny post). I połowa: do zdekodowania elementów tablicy 0 i 1, mógłbyś użyć pętli FOR, zamiast duplikować cały kod (jest to krótki kod, ale może być bardziej wydajny, ponieważ tak jest, SPLIT jest wykonywany dwukrotnie ).
Cyberknight
4

wszystkie funkcje jwt.io nie obsługują wszystkich języków. W NodeJs możesz użyć

var decoded = jwt.decode(token);
Jithin Vijayan
źródło
1
Bez biblioteki po prostu wykonujesz dekodowanie base64 w drugiej części tokena {var payload = token.split ('.') [1]); } Następnie wykonaj dekodowanie base64 {var decodedData = atob (ładunek); }
Jithin Vijayan,
4

Znalazłem ten kod na jwt.io i działa dobrze.

//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;
  }
}

W niektórych przypadkach (niektóre platformy programistyczne)
najlepsza odpowiedź (na razie) napotyka problem nieprawidłowej długości base64.
Potrzebowałem więc bardziej stabilnego sposobu.

Mam nadzieję, że ci to pomoże.

Nao Ito
źródło
2

Zarówno Guy, jak i Peheje już odpowiedzieli na pytanie. Dla totalnego początkującego, takiego jak ja, pomocne było również zdefiniowanie linii importu w przykładzie.

Zajęło mi również kilka minut, aby dowiedzieć się, że token to pełny zestaw poświadczeń, który jest wysyłany z powrotem (cały token JWT, a nie tylko jego część idToken). Prosto, gdy się zorientujesz ...

import jwt_decode from 'jwt-decode';

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

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

Campo Blanco
źródło
2
Umieszczenie tej samej odpowiedzi co inny użytkownik, która również jest sprzeczna z tym, o co poprosił OP, nie jest bardzo pomocne
Cacoon,
2

Proste rozwiązanie NodeJS do dekodowania tokenu WWW 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}`)
Derek Soike
źródło
2

Odpowiedź oparta na GitHub - auth0 / jwt-decode . Zmieniono wejście / wyjście, aby uwzględnić podział ciągów i zwracać obiekt {nagłówek, ładunek, podpis}, dzięki czemu można po prostu przekazać cały token.

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])
        };

    };
kalingasan
źródło
1

Oto bardziej bogate w funkcje rozwiązanie, które właśnie stworzyłem po przestudiowaniu tego pytania:

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.',
        };
    }
};

Oto kilka przykładów użycia:

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
}

Nie udało mi się uruchomić tego narzędzia w narzędziu Fragment kodu StackOverflow, ale oto mniej więcej to, co zobaczysz, jeśli uruchomisz ten kod:

wprowadź opis zdjęcia tutaj

Sprawiłem, że parseJwtfunkcja zawsze zwraca obiekt (do pewnego stopnia z powodów pisania statycznego).

Pozwala to na użycie składni, takiej jak:

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

Następnie możesz przetestować w czasie wykonywania pod kątem określonych rodzajów błędów i uniknąć kolizji nazw.

Jeśli ktoś może pomyśleć o jakimkolwiek niewielkim wysiłku, zmianach tego kodu o wysokiej wartości, z przyjemnością zedytuję moją odpowiedź next(person).

agm1984
źródło
0

Na podstawie odpowiedzi tutaj i tutaj :

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);
};
webjay
źródło
-1

Uruchamiając Javascript node.js express Musiałem najpierw zainstalować pakiet w następujący sposób:

npm install jwt-decode --save

następnie w moim kodzie app.js pobierz pakiet:

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

Następnie uruchom kod:

let jwt_decoded = jwt_decode(jwt_source);

Następnie magia:

console.log('sub:',jwt_decoded.sub);
David White
źródło
4
pamiętajcie „bez korzystania z biblioteki”
Olaf
1
ok, w porządku. Jednak napotkałem ten sam problem i nie miałem ograniczenia, że ​​nie mogę korzystać z biblioteki. To zadziałało dla mnie. Zostawiam to, ponieważ może ktoś ma podobny problem i nie ma takich samych ograniczeń.
David White