JWT Overview
- Overview
-
Decode with browser JavaScript
- Check expiration
- Decode with
keypairs
CLI - Example
A JWT (JSON Web Token) is a “compressed” JWS (JSON Web Signature) with three Base64-encoded parts:
JWT/JWS Component | Description |
---|---|
(Protected) Header | contains metadata related to verification, such as the Key ID (kid) and signature algorithm (alg) |
(Claims) Payload | data describing the user, account, bot, etc and related access grants |
Signature | the RSA, ECDSA, HMAC (shared secret), or other signature of <protected>.<payload> (note that ECDSA signatures will be different even when encoding the same data) |
Decoding or Parsing a JWT is simply a matter of converting from the “compressed” Base64-encoded form that looks like this:
xxxx.yyyy.zzzz
to the “decompressed” form - known as JWS - that looks like this:
{
"protected": "<base64-encoded-headers>",
"header": { "typ": "JWT", "kid": "..." },
"payload": "<base64-encoded-claims>",
"claims": { "iss": "https://example.com", "sub": "..." },
"signature": "<base64-encoded-signature>"
}
Since the ordering of JSON is not strictly deterministic (the keys may be listed alphabetically, or by parse order, or seemingly random), the base64-encoded versions of “header” (called “protected”) and “claims” (called “payload”) must be generated for signing and preserved for verification.
Decode JWT via CLI
keypairs is a command line tool for generating, decoding, and verifying JWTs:
keypairs inspect 'xxxx.yyyy.zzzz'
JWT Parser in JavaScript
Here’s 30 lines of vanilla JS for decoding a JWT:
Usage:
// Parse
let jws = parseJwt("xxxx.yyyy.zzzz");
// Check expiration
let secondsSinceEpoch = Math.ceil(Date.now() / 1000)
let fresh = jws.claims.exp > secondsSinceEpoch;
Source:
function parseJwt(jwt) {
let parts = jwt.split(".");
let jws = {
protected: parts[0],
payload: parts[1],
signature: parts[2],
};
jws.header = urlBase64ToJson(jws.protected);
jws.claims = urlBase64ToJson(jws.payload);
return jws;
};
// Ironically, JavaScript's native Base64 implementation isn't URL-safe. :)
function urlBase64ToBase64(str) {
let r = str % 4;
if (2 === r) {
str += "==";
} else if (3 === r) {
str += "=";
}
return str.replace(/-/g, "+").replace(/_/g, "/");
}
function urlBase64ToJson(u64) {
let b64 = urlBase64ToBase64(u64);
let str = atob(b64);
return JSON.parse(str);
}
License:
/**
* @license
* parse-jwt.js - decode / parse / decompress a JWT into a JWS (plus JSON)
*
* Written in 2020 by AJ ONeal <coolaj86@gmail.com>
* To the extent possible under law, the author(s) have dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along with
* this software. If not, see <https://creativecommons.org/publicdomain/zero/1.0/>.
*/
Example of how to Decode a JWT
Here’s a more complete example of what a JWT (compressed JWS) looks like:
eyJhbGciOiJFUzI1NiIsImtpZCI6Ik5nWmlZZ0xCeDZVZ09zU25RYnZqYlMzSS01OGdwajVHOVlvdXZOMGN3U2MiLCJ0eXAiOiJKV1QifQ.eyJhbXIiOlsiY2hhbGxlbmdlOmVtYWlsIl0sImF1dGhfdGltZSI6MTY1NTc2MTU3MCwiZW1haWwiOiJtZUBleGFtcGxlLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjMzMTE3Njc5NTMsImdpdmVuX25hbWUiOiJNZSIsImlhdCI6MTY1NTc2MTU3MCwiaXNzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSIsImp0aSI6IlJuREJDUVJtVUJraDNtOEsyYnJlSFcifQ.mbUjGeKnScKfqiGuQM85EcKZtb0ONLaRDnXWwJ0GMKAHQKJGERPg0mnk9Vf6sSdmYZYc0CTCgBS92K_Wlw0PuA
-
Split the token string on
.
(period) to produce the component parts of the JWS:let jwt = "xxxx.yyyy.zzzz"; let [protected, payload, signature] = token.split('.'); let jws = { protected: protected, payload: payload, signature: signature, }
# The Protected Header eyJhbGciOiJFUzI1NiIsImtpZCI6Ik5nWmlZZ0xCeDZVZ09zU25RYnZqYlMzSS01OGdwajVHOVlvdXZOMGN3U2MiLCJ0eXAiOiJKV1QifQ # The Claims Payload eyJhbXIiOlsiY2hhbGxlbmdlOmVtYWlsIl0sImF1dGhfdGltZSI6MTY1NTc2MTU3MCwiZW1haWwiOiJtZUBleGFtcGxlLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjMzMTE3Njc5NTMsImdpdmVuX25hbWUiOiJNZSIsImlhdCI6MTY1NTc2MTU3MCwiaXNzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSIsImp0aSI6IlJuREJDUVJtVUJraDNtOEsyYnJlSFcifQ # The Signature - the result of sign(protected + "." + payload) mbUjGeKnScKfqiGuQM85EcKZtb0ONLaRDnXWwJ0GMKAHQKJGERPg0mnk9Vf6sSdmYZYc0CTCgBS92K_Wlw0PuA
-
Decode the url-safe base64 into JSON:
jws.header = JSON.parse( decodeUrlBase64( jws.payload ) ); jws.claims = JSON.parse( decodeUrlBase64( jws.payload ) );
Example of Parsed Result
And here’s a more complete example of a JWS (plus decoded JSON):
{
"protected": "eyJhbGciOiJFUzI1NiIsImtpZCI6Ik5nWmlZZ0xCeDZVZ09zU25RYnZqYlMzSS01OGdwajVHOVlvdXZOMGN3U2MiLCJ0eXAiOiJKV1QifQ",
"header": {
"alg": "ES256",
"kid": "NgZiYgLBx6UgOsSnQbvjbS3I-58gpj5G9YouvN0cwSc",
"typ": "JWT"
},
"payload": "eyJhbXIiOlsiY2hhbGxlbmdlOmVtYWlsIl0sImF1dGhfdGltZSI6MTY1NTc2MTU3MCwiZW1haWwiOiJtZUBleGFtcGxlLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjMzMTE3Njc5NTMsImdpdmVuX25hbWUiOiJNZSIsImlhdCI6MTY1NTc2MTU3MCwiaXNzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSIsImp0aSI6IlJuREJDUVJtVUJraDNtOEsyYnJlSFcifQ",
"claims": {
"amr": [
"challenge:email"
],
"auth_time": 1655761570,
"email": "me@example.com",
"email_verified": true,
"exp": 3311767953,
"given_name": "Me",
"iat": 1655761570,
"iss": "https://example.com",
"jti": "RnDBCQRmUBkh3m8K2breHW"
},
"signature": "mbUjGeKnScKfqiGuQM85EcKZtb0ONLaRDnXWwJ0GMKAHQKJGERPg0mnk9Vf6sSdmYZYc0CTCgBS92K_Wlw0PuA"
}
The description of the various headers and claims - such as amr
, auth_time
, and jti
- can be found in the OpenID Connect Core 1.0 incorporating errata set 1 under Section 5.1 Standard Claims: