Tools

Input

Paste your JWT here. Everything is client-side, so nothing to worry about.

Output

The Code


const descriptions = {
    iss: 'Issuer',
    sub: 'Subject',
    aud: 'Audience',
    exp: 'Expiration time',
    nbf: 'Not Before',
    iat: 'Issued at',
    jwt: 'JWT ID',
    typ: 'Token type',
    cty: 'Content type',
    alg: 'Message authentication code (MAC) algorithm'
}


const render = (data, part) => {
    const table = document.createElement('table')
    table.innerHTML = `<caption>${part}</caption><tr><th>Property</th><th>Value</th><th>Description</th></tr>`
    table.classList.add('jwt-decoder')

    const output = document.querySelector('div.output')
    output.appendChild(table)

    for (let [property, value] of Object.entries(data)) {
        // detect dates (educated guess)
        if (Number.isInteger(value)
            && value * 1000 > new Date(1974, 10 - 1, 11)
            && value * 1000 < new Date(2050, 0, 1)) {
            value = new Date(value * 1000).toLocaleString()
        }

        // render row
        const row = table.insertRow()
        row.insertCell().innerText = property
        row.insertCell().innerText = typeof value == 'object' ? JSON.stringify(value) : value
        row.insertCell().innerText = descriptions[property] || ''
    }
}

const update = async jwt => {
    // split JWT
    const parts = jwt.split('.')

    // extract the header, payload and signature
    const [header, payload, signature] = [
        JSON.parse(atob(parts[0])),
        JSON.parse(atob(parts[1])),
        parts[2]            // FIXME *can* be base64 encoded
    ]

    // clear all previous tables

    const output = document.querySelector('div.output')
    output.textContent = ''

    const secret = document.querySelector('input[name="secret"]').value

    // render JWT
    render(header, 'header')
    render(payload, 'payload')
    render({ signature }, 'signature')

    // verify header + payload
    const data = new TextEncoder().encode(`${parts[0]}.${parts[1]}`)
    const key = await crypto.subtle.importKey('jwk', secret, {
        name: 'RSASSA-PKCS1-v1_5',      // or HMAC?
        hash: { name: header.alg }
    }, false, ['verify'])

    const isValid = await crypto.subtle.verify({ name: 'prRSASSA-PKCS1-v1_5' }, key, signature, data)
    document.querySelector('.signature td-something').textContent = valid ? '<span class="text-success">✔</span>' : '<span class="text-danger">🗙</span>'
}

const params = new URLSearchParams(document.location.search)
const jwt = params.get('jwt') || params.get('token') 
if(jwt) {
    document.querySelector('textarea').value = jwt
}

update(document.querySelector('textarea').value)
jwt-decoder.js