function initJwtEncoder() { const statusEl = document.getElementById("jwtEncodeStatus"); const algEl = document.getElementById("jwtEncodeAlg"); const headerEl = document.getElementById("jwtHeaderInput"); const payloadEl = document.getElementById("jwtPayloadInput"); const secretEl = document.getElementById("jwtSecretInput"); const outputEl = document.getElementById("jwtTokenOutput"); const buildBtn = document.getElementById("jwtEncodeBuildBtn"); const copyBtn = document.getElementById("jwtEncodeCopyBtn"); const clearBtn = document.getElementById("jwtEncodeClearBtn"); if (!algEl || !headerEl || !payloadEl || !outputEl || !buildBtn) return; function setStatus(text, isError) { if (!statusEl) return; statusEl.textContent = text; statusEl.classList.toggle("status-error", !!isError); statusEl.classList.toggle("status-success", !isError); } function updateButtons() { if (!copyBtn) return; copyBtn.disabled = !outputEl.value.trim(); } function encodeBase64Url(text) { const bytes = new TextEncoder().encode(text); let binary = ""; for (let i = 0; i < bytes.length; i += 1) { binary += String.fromCharCode(bytes[i]); } return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""); } async function signHs256(data, secret) { const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"] ); const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(data)); const bytes = new Uint8Array(signature); let binary = ""; for (let i = 0; i < bytes.length; i += 1) { binary += String.fromCharCode(bytes[i]); } return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""); } function parseJson(text, label) { try { return JSON.parse(text); } catch (err) { throw new Error(`Invalid ${label} JSON`); } } async function buildToken() { const alg = algEl.value; let header; let payload; try { header = parseJson(headerEl.value.trim(), "header"); payload = parseJson(payloadEl.value.trim(), "payload"); } catch (err) { setStatus(err.message, true); outputEl.value = ""; updateButtons(); return; } header.alg = alg; if (!header.typ) header.typ = "JWT"; headerEl.value = JSON.stringify(header, null, 2); const headerB64 = encodeBase64Url(JSON.stringify(header)); const payloadB64 = encodeBase64Url(JSON.stringify(payload)); const unsigned = `${headerB64}.${payloadB64}`; try { if (alg === "none") { outputEl.value = `${unsigned}.`; setStatus("JWT encoded with alg none (unsigned).", true); } else { const secret = secretEl.value; if (!secret.trim()) { setStatus("Secret is required for HS256.", true); outputEl.value = ""; updateButtons(); return; } const signature = await signHs256(unsigned, secret); outputEl.value = `${unsigned}.${signature}`; setStatus("JWT encoded with HS256."); } } catch (err) { outputEl.value = ""; setStatus("Failed to encode token.", true); } updateButtons(); } async function copyToken() { if (!outputEl.value.trim() || !copyBtn) return; await navigator.clipboard.writeText(outputEl.value); const original = copyBtn.textContent; copyBtn.textContent = "Copied"; setStatus("Token copied."); setTimeout(() => { copyBtn.textContent = original; }, 1200); } function clearAll() { outputEl.value = ""; setStatus("Enter header, payload, and optional secret."); updateButtons(); } buildBtn.addEventListener("click", () => { void buildToken(); }); if (copyBtn) { copyBtn.addEventListener("click", () => { void copyToken(); }); } if (clearBtn) clearBtn.addEventListener("click", clearAll); algEl.addEventListener("change", () => { if (algEl.value === "none") { setStatus("alg none creates an unsigned token.", true); } else { setStatus("HS256 selected."); } }); updateButtons(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initJwtEncoder); } else { initJwtEncoder(); }