UNPKG

@shlomiatar/nano-jwt

Version:

A tiny, minimalistic HS256/HS512 jwt verifier using WebCryptoAPIs (for browser/bun/cloudflare)

34 lines (33 loc) 1.49 kB
// src/index.ts var [dec, enc] = [new TextDecoder, new TextEncoder]; var b64uToU8 = (s) => new Uint8Array(atob(s.replace(/-/g, "+").replace(/_/g, "/")).split("").map((c) => c.charCodeAt(0))); var b64uToJson = (s) => JSON.parse(dec.decode(b64uToU8(s))); var jwt = (secret, alg = "HS256", matcher) => { let _key; const hash = `SHA-${alg.substring(2)}`; const matcherFn = matcher ? typeof matcher === "function" ? matcher : (p) => Object.keys(matcher ?? {}).every((k) => p[k] === matcher[k]) : () => true; return { verify: async (token) => { try { _key = _key ?? await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash }, false, ["verify"]); const [enc_alg, enc_payload, enc_sign, ...rest] = token.split("."); if (!enc_alg || !enc_payload || !enc_sign || rest.length) return [false, { error: "invalid_format" }]; if (b64uToJson(enc_alg).alg != alg) return [false, { error: "unsupported_algorithm" }]; if (!await crypto.subtle.verify("HMAC", _key, b64uToU8(enc_sign), enc.encode(`${enc_alg}.${enc_payload}`))) return [false, { error: "bad_signature" }]; const payload = b64uToJson(enc_payload); const result = matcherFn(payload); if (result !== true) return [false, { error: result || "invalid_payload" }]; return [true, payload]; } catch { return [false, { error: "verification_failed" }]; } } }; }; export { jwt };