// Reading a JWT without verifying it. // // Every token this app decodes was either signed by a server it just spoke to // over TLS, or is about to be handed straight back to that server. Nothing here // decides anything: the payloads are read so a page can show them, and the // signature is checked by whichever server receives the token. /** * @typedef {Object} DecodedJwt * @property {Record} header * @property {Record} payload */ /** * @param {string} segment - base64url, unpadded * @returns {Record} */ function decodeSegment(segment) { const padded = segment.replace(/-/g, '+').replace(/_/g, '/'); return JSON.parse(atob(padded.padEnd(Math.ceil(padded.length / 4) * 4, '='))); } /** * @param {string} jwt * @returns {DecodedJwt|null} null for anything that is not one */ export function decodeJwt(jwt) { try { const [header, payload] = jwt.split('.'); return { header: decodeSegment(header), payload: decodeSegment(payload) }; } catch { return null; } } /** * The `exp` a JWT states, in epoch milliseconds, or 0 for a token that states * none. * @param {string} jwt * @returns {number} */ export function expiryOf(jwt) { const exp = decodeJwt(jwt)?.payload.exp; return typeof exp === 'number' ? exp * 1000 : 0; }