[{"data":1,"prerenderedAt":883},["ShallowReactive",2],{"navigation":3,"-jwt-jws":167,"-jwt-jws-surround":880},[4,22,78,106,141,148],{"title":5,"path":6,"stem":7,"children":8},"Introduction","\u002Fgetting-started","0.Getting-Started\u002F0.index",[9,10,14,18],{"title":5,"path":6,"stem":7},{"title":11,"path":12,"stem":13},"Installation","\u002Fgetting-started\u002Finstallation","0.Getting-Started\u002F1.installation",{"title":15,"path":16,"stem":17},"Quickstart","\u002Fgetting-started\u002Fquickstart","0.Getting-Started\u002F2.quickstart",{"title":19,"path":20,"stem":21},"Core concepts","\u002Fgetting-started\u002Fcore-concepts","0.Getting-Started\u002F3.core-concepts",{"title":23,"path":24,"stem":25,"children":26,"icon":28},"JWT","\u002Fjwt","1.JWT\u002F0.index",[27,29,52],{"title":23,"path":24,"stem":25,"icon":28},"i-carbon-certificate",{"title":30,"path":31,"stem":32,"children":33,"icon":35},"JWS","\u002Fjwt\u002Fjws","1.JWT\u002F1.JWS\u002F0.index",[34,36,40,44,48],{"title":30,"path":31,"stem":32,"icon":35},"i-carbon-document-signed",{"title":37,"path":38,"stem":39},"Signing","\u002Fjwt\u002Fjws\u002Fsigning","1.JWT\u002F1.JWS\u002F1.signing",{"title":41,"path":42,"stem":43},"Verifying","\u002Fjwt\u002Fjws\u002Fverifying","1.JWT\u002F1.JWS\u002F2.verifying",{"title":45,"path":46,"stem":47},"Multi-signature","\u002Fjwt\u002Fjws\u002Fmulti-signature","1.JWT\u002F1.JWS\u002F3.multi-signature",{"title":49,"path":50,"stem":51},"Algorithms","\u002Fjwt\u002Fjws\u002Falgorithms","1.JWT\u002F1.JWS\u002F4.algorithms",{"title":53,"path":54,"stem":55,"children":56,"icon":58},"JWE","\u002Fjwt\u002Fjwe","1.JWT\u002F2.JWE\u002F0.index",[57,59,63,67,71,75],{"title":53,"path":54,"stem":55,"icon":58},"i-carbon-locked",{"title":60,"path":61,"stem":62},"Encrypting","\u002Fjwt\u002Fjwe\u002Fencrypting","1.JWT\u002F2.JWE\u002F1.encrypting",{"title":64,"path":65,"stem":66},"Decrypting","\u002Fjwt\u002Fjwe\u002Fdecrypting","1.JWT\u002F2.JWE\u002F2.decrypting",{"title":68,"path":69,"stem":70},"Multi-recipient","\u002Fjwt\u002Fjwe\u002Fmulti-recipient","1.JWT\u002F2.JWE\u002F3.multi-recipient",{"title":72,"path":73,"stem":74},"ECDH-ES and end-to-end encryption","\u002Fjwt\u002Fjwe\u002Fecdh-es","1.JWT\u002F2.JWE\u002F4.ecdh-es",{"title":49,"path":76,"stem":77},"\u002Fjwt\u002Fjwe\u002Falgorithms","1.JWT\u002F2.JWE\u002F5.algorithms",{"title":79,"path":80,"stem":81,"children":82,"icon":84},"Examples","\u002Fexamples","10.Examples\u002F0.index",[83,85,90,94,98,102],{"title":79,"path":80,"stem":81,"icon":84},"i-carbon-code-reference",{"title":86,"path":87,"stem":88,"icon":89},"Authentication basics","\u002Fexamples\u002Fauthentication-basics","10.Examples\u002F1.authentication-basics","i-lucide-code",{"title":91,"path":92,"stem":93,"icon":89},"Consuming a JWKS endpoint","\u002Fexamples\u002Fjwks-endpoint","10.Examples\u002F2.jwks-endpoint",{"title":95,"path":96,"stem":97,"icon":89},"Refresh token pattern","\u002Fexamples\u002Frefresh-token-pattern","10.Examples\u002F3.refresh-token-pattern",{"title":99,"path":100,"stem":101,"icon":89},"End-to-end encryption","\u002Fexamples\u002Fend-to-end-encryption","10.Examples\u002F4.end-to-end-encryption",{"title":103,"path":104,"stem":105,"icon":89},"Signed receipts","\u002Fexamples\u002Fsigned-receipts","10.Examples\u002F5.signed-receipts",{"title":107,"path":108,"stem":109,"children":110,"icon":112},"JWK","\u002Fjwk","2.JWK\u002F0.index",[111,113,117,121,125,129,133,137],{"title":107,"path":108,"stem":109,"icon":112},"i-carbon-two-factor-authentication",{"title":114,"path":115,"stem":116},"Generating keys","\u002Fjwk\u002Fgenerating","2.JWK\u002F1.generating",{"title":118,"path":119,"stem":120},"Importing and exporting","\u002Fjwk\u002Fimport-export","2.JWK\u002F2.import-export",{"title":122,"path":123,"stem":124},"PEM conversion","\u002Fjwk\u002Fpem","2.JWK\u002F3.pem",{"title":126,"path":127,"stem":128},"Key wrapping","\u002Fjwk\u002Fwrapping","2.JWK\u002F4.wrapping",{"title":130,"path":131,"stem":132},"Password derivation","\u002Fjwk\u002Fpassword-derivation","2.JWK\u002F5.password-derivation",{"title":134,"path":135,"stem":136},"JWK Sets","\u002Fjwk\u002Fjwk-sets","2.JWK\u002F6.jwk-sets",{"title":138,"path":139,"stem":140},"JWK cache","\u002Fjwk\u002Fcache","2.JWK\u002F7.cache",{"title":142,"path":143,"stem":144,"children":145,"icon":147},"Utilities","\u002Futilities","3.Utilities\u002F0.index",[146],{"title":142,"path":143,"stem":144,"icon":147},"i-carbon-tool-box",{"title":149,"path":150,"stem":151,"children":152,"icon":154},"Adapters","\u002Fadapters","99.Adapters\u002F0.index",[153,155,159,163],{"title":149,"path":150,"stem":151,"icon":154},"i-carbon-plug",{"title":156,"path":157,"stem":158},"H3 sessions","\u002Fadapters\u002Fh3-sessions","99.Adapters\u002F1.h3-sessions",{"title":160,"path":161,"stem":162},"Lifecycle hooks","\u002Fadapters\u002Fhooks","99.Adapters\u002F2.hooks",{"title":164,"path":165,"stem":166},"Lower-level functions","\u002Fadapters\u002Flower-level","99.Adapters\u002F3.lower-level",{"id":168,"title":30,"body":169,"description":203,"extension":875,"meta":876,"navigation":877,"path":31,"seo":878,"stem":32,"__hash__":879},"content\u002F1.JWT\u002F1.JWS\u002F0.index.md",{"type":170,"value":171,"toc":868,"icon":35},"minimark",[172,177,194,197,251,256,406,433,437,444,514,535,539,542,587,590,670,676,680,686,729,789,799,822,826,864],[173,174,176],"h1",{"id":175},"jws-signed-tokens","JWS — signed tokens",[178,179,180,181,185,186,193],"p",{},"A ",[182,183,184],"strong",{},"JSON Web Signature"," (",[187,188,192],"a",{"href":189,"rel":190},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc7515",[191],"nofollow","RFC 7515",") is a JWT whose payload is signed: the data travels in the clear (base64url-encoded), but anyone who tampers with it invalidates the signature.",[178,195,196],{},"Import:",[198,199,204],"pre",{"className":200,"code":201,"language":202,"meta":203,"style":203},"language-ts shiki shiki-themes github-light github-dark github-dark","import { sign, verify } from \"unjwt\u002Fjws\";\n\u002F\u002F or from the flat barrel:\nimport { sign, verify } from \"unjwt\";\n","ts","",[205,206,207,230,237],"code",{"__ignoreMap":203},[208,209,212,216,220,223,227],"span",{"class":210,"line":211},"line",1,[208,213,215],{"class":214},"so5gQ","import",[208,217,219],{"class":218},"slsVL"," { sign, verify } ",[208,221,222],{"class":214},"from",[208,224,226],{"class":225},"sfrk1"," \"unjwt\u002Fjws\"",[208,228,229],{"class":218},";\n",[208,231,233],{"class":210,"line":232},2,[208,234,236],{"class":235},"sCsY4","\u002F\u002F or from the flat barrel:\n",[208,238,240,242,244,246,249],{"class":210,"line":239},3,[208,241,215],{"class":214},[208,243,219],{"class":218},[208,245,222],{"class":214},[208,247,248],{"class":225}," \"unjwt\"",[208,250,229],{"class":218},[252,253,255],"h2",{"id":254},"basic-usage","Basic usage",[198,257,260],{"className":200,"code":258,"filename":259,"language":202,"meta":203,"style":203},"import { sign, verify } from \"unjwt\u002Fjws\";\nimport { generateJWK } from \"unjwt\u002Fjwk\";\n\nconst key = await generateJWK(\"HS256\");\n\nconst token = await sign({ sub: \"user_1\" }, key, { expiresIn: \"1h\" });\nconst { payload } = await verify(token, key);\n\nconsole.log(payload);\n\u002F\u002F { sub: \"user_1\", iat: 1736860800, exp: 1736864400 }\n","sign-verify.ts",[205,261,262,274,288,294,323,328,358,383,388,400],{"__ignoreMap":203},[208,263,264,266,268,270,272],{"class":210,"line":211},[208,265,215],{"class":214},[208,267,219],{"class":218},[208,269,222],{"class":214},[208,271,226],{"class":225},[208,273,229],{"class":218},[208,275,276,278,281,283,286],{"class":210,"line":232},[208,277,215],{"class":214},[208,279,280],{"class":218}," { generateJWK } ",[208,282,222],{"class":214},[208,284,285],{"class":225}," \"unjwt\u002Fjwk\"",[208,287,229],{"class":218},[208,289,290],{"class":210,"line":239},[208,291,293],{"emptyLinePlaceholder":292},true,"\n",[208,295,297,300,304,307,310,314,317,320],{"class":210,"line":296},4,[208,298,299],{"class":214},"const",[208,301,303],{"class":302},"suiK_"," key",[208,305,306],{"class":214}," =",[208,308,309],{"class":214}," await",[208,311,313],{"class":312},"shcOC"," generateJWK",[208,315,316],{"class":218},"(",[208,318,319],{"class":225},"\"HS256\"",[208,321,322],{"class":218},");\n",[208,324,326],{"class":210,"line":325},5,[208,327,293],{"emptyLinePlaceholder":292},[208,329,331,333,336,338,340,343,346,349,352,355],{"class":210,"line":330},6,[208,332,299],{"class":214},[208,334,335],{"class":302}," token",[208,337,306],{"class":214},[208,339,309],{"class":214},[208,341,342],{"class":312}," sign",[208,344,345],{"class":218},"({ sub: ",[208,347,348],{"class":225},"\"user_1\"",[208,350,351],{"class":218}," }, key, { expiresIn: ",[208,353,354],{"class":225},"\"1h\"",[208,356,357],{"class":218}," });\n",[208,359,361,363,366,369,372,375,377,380],{"class":210,"line":360},7,[208,362,299],{"class":214},[208,364,365],{"class":218}," { ",[208,367,368],{"class":302},"payload",[208,370,371],{"class":218}," } ",[208,373,374],{"class":214},"=",[208,376,309],{"class":214},[208,378,379],{"class":312}," verify",[208,381,382],{"class":218},"(token, key);\n",[208,384,386],{"class":210,"line":385},8,[208,387,293],{"emptyLinePlaceholder":292},[208,389,391,394,397],{"class":210,"line":390},9,[208,392,393],{"class":218},"console.",[208,395,396],{"class":312},"log",[208,398,399],{"class":218},"(payload);\n",[208,401,403],{"class":210,"line":402},10,[208,404,405],{"class":235},"\u002F\u002F { sub: \"user_1\", iat: 1736860800, exp: 1736864400 }\n",[178,407,408,409,412,413,416,417,420,421,424,425,428,429,432],{},"The algorithm (",[205,410,411],{},"HS256",") was read from ",[205,414,415],{},"key.alg",". The ",[205,418,419],{},"iat"," and ",[205,422,423],{},"exp"," claims were added because you passed ",[205,426,427],{},"expiresIn",". Claim validation ran automatically on ",[205,430,431],{},"verify"," because the payload is a JSON object.",[252,434,436],{"id":435},"signed-in-the-clear-not-encrypted","Signed-in-the-clear, not encrypted",[178,438,439,440,443],{},"A JWS is ",[182,441,442],{},"not private",". Decoding the second segment of the token with any base64url decoder reveals the payload:",[198,445,447],{"className":200,"code":446,"language":202,"meta":203,"style":203},"import { base64UrlDecode } from \"unjwt\u002Futils\";\n\nconst [, payloadSegment] = token.split(\".\");\nconsole.log(base64UrlDecode(payloadSegment));\n\u002F\u002F {\"sub\":\"user_1\",\"iat\":1736860800,\"exp\":1736864400}\n",[205,448,449,463,467,495,509],{"__ignoreMap":203},[208,450,451,453,456,458,461],{"class":210,"line":211},[208,452,215],{"class":214},[208,454,455],{"class":218}," { base64UrlDecode } ",[208,457,222],{"class":214},[208,459,460],{"class":225}," \"unjwt\u002Futils\"",[208,462,229],{"class":218},[208,464,465],{"class":210,"line":232},[208,466,293],{"emptyLinePlaceholder":292},[208,468,469,471,474,477,480,482,485,488,490,493],{"class":210,"line":239},[208,470,299],{"class":214},[208,472,473],{"class":218}," [, ",[208,475,476],{"class":302},"payloadSegment",[208,478,479],{"class":218},"] ",[208,481,374],{"class":214},[208,483,484],{"class":218}," token.",[208,486,487],{"class":312},"split",[208,489,316],{"class":218},[208,491,492],{"class":225},"\".\"",[208,494,322],{"class":218},[208,496,497,499,501,503,506],{"class":210,"line":296},[208,498,393],{"class":218},[208,500,396],{"class":312},[208,502,316],{"class":218},[208,504,505],{"class":312},"base64UrlDecode",[208,507,508],{"class":218},"(payloadSegment));\n",[208,510,511],{"class":210,"line":325},[208,512,513],{"class":235},"\u002F\u002F {\"sub\":\"user_1\",\"iat\":1736860800,\"exp\":1736864400}\n",[178,515,516,517,420,520,523,524,527,528,531,532,534],{},"What a JWS gives you is ",[182,518,519],{},"integrity",[182,521,522],{},"authenticity",": flipping a bit in the payload changes its base64url string, which invalidates the signature, which makes ",[205,525,526],{},"verify()"," throw. If you need ",[182,529,530],{},"confidentiality"," instead (or as well), use ",[187,533,53],{"href":54},".",[252,536,538],{"id":537},"symmetric-vs-asymmetric","Symmetric vs. asymmetric",[178,540,541],{},"Two families of signing keys, chosen by the algorithm:",[543,544,545,565],"ul",{},[546,547,548,185,551,553,554,553,557,560,561,564],"li",{},[182,549,550],{},"Symmetric",[205,552,411],{},", ",[205,555,556],{},"HS384",[205,558,559],{},"HS512",") — one secret key signs ",[182,562,563],{},"and"," verifies. Use when signer and verifier are the same party (e.g. your API signs tokens and your API verifies them).",[546,566,567,185,570,553,573,553,576,553,579,582,583,586],{},[182,568,569],{},"Asymmetric",[205,571,572],{},"RS*",[205,574,575],{},"PS*",[205,577,578],{},"ES*",[205,580,581],{},"Ed25519","\u002F",[205,584,585],{},"EdDSA",") — a private key signs, a public key verifies. Use when anyone should be able to verify without holding signing power (e.g. you publish a JWKS endpoint; third parties verify).",[178,588,589],{},"Asymmetric round trip:",[198,591,593],{"className":200,"code":592,"language":202,"meta":203,"style":203},"const { privateKey, publicKey } = await generateJWK(\"ES256\");\n\nconst token = await sign({ sub: \"user_1\" }, privateKey, { expiresIn: \"1h\" });\nconst { payload } = await verify(token, publicKey);\n",[205,594,595,624,628,651],{"__ignoreMap":203},[208,596,597,599,601,604,606,609,611,613,615,617,619,622],{"class":210,"line":211},[208,598,299],{"class":214},[208,600,365],{"class":218},[208,602,603],{"class":302},"privateKey",[208,605,553],{"class":218},[208,607,608],{"class":302},"publicKey",[208,610,371],{"class":218},[208,612,374],{"class":214},[208,614,309],{"class":214},[208,616,313],{"class":312},[208,618,316],{"class":218},[208,620,621],{"class":225},"\"ES256\"",[208,623,322],{"class":218},[208,625,626],{"class":210,"line":232},[208,627,293],{"emptyLinePlaceholder":292},[208,629,630,632,634,636,638,640,642,644,647,649],{"class":210,"line":239},[208,631,299],{"class":214},[208,633,335],{"class":302},[208,635,306],{"class":214},[208,637,309],{"class":214},[208,639,342],{"class":312},[208,641,345],{"class":218},[208,643,348],{"class":225},[208,645,646],{"class":218}," }, privateKey, { expiresIn: ",[208,648,354],{"class":225},[208,650,357],{"class":218},[208,652,653,655,657,659,661,663,665,667],{"class":210,"line":296},[208,654,299],{"class":214},[208,656,365],{"class":218},[208,658,368],{"class":302},[208,660,371],{"class":218},[208,662,374],{"class":214},[208,664,309],{"class":214},[208,666,379],{"class":312},[208,668,669],{"class":218},"(token, publicKey);\n",[178,671,672,673,534],{},"Full algorithm table: ",[187,674,675],{"href":50},"JWS algorithms →",[252,677,679],{"id":678},"standard-claims-automatic","Standard claims, automatic",[178,681,682,683,685],{},"When the payload is an object, ",[205,684,526],{}," runs claim validation automatically (no opt-in needed):",[543,687,688,697,703,708],{},[546,689,690,692,693,696],{},[205,691,423],{}," — rejected if expired (past current time minus ",[205,694,695],{},"clockTolerance",").",[546,698,699,702],{},[205,700,701],{},"nbf"," — rejected if used before its \"not before\" timestamp.",[546,704,705,707],{},[205,706,419],{}," — must be a valid number if present.",[546,709,710,553,713,553,716,553,719,553,722,553,725,728],{},[205,711,712],{},"aud",[205,714,715],{},"iss",[205,717,718],{},"sub",[205,720,721],{},"typ",[205,723,724],{},"maxTokenAge",[205,726,727],{},"requiredClaims"," — checked when you pass the matching option.",[198,730,732],{"className":200,"code":731,"language":202,"meta":203,"style":203},"const { payload } = await verify(token, key, {\n  issuer: \"https:\u002F\u002Fauth.example.com\",\n  audience: \"my-api\",\n  maxTokenAge: \"24h\",\n});\n",[205,733,734,753,764,774,784],{"__ignoreMap":203},[208,735,736,738,740,742,744,746,748,750],{"class":210,"line":211},[208,737,299],{"class":214},[208,739,365],{"class":218},[208,741,368],{"class":302},[208,743,371],{"class":218},[208,745,374],{"class":214},[208,747,309],{"class":214},[208,749,379],{"class":312},[208,751,752],{"class":218},"(token, key, {\n",[208,754,755,758,761],{"class":210,"line":232},[208,756,757],{"class":218},"  issuer: ",[208,759,760],{"class":225},"\"https:\u002F\u002Fauth.example.com\"",[208,762,763],{"class":218},",\n",[208,765,766,769,772],{"class":210,"line":239},[208,767,768],{"class":218},"  audience: ",[208,770,771],{"class":225},"\"my-api\"",[208,773,763],{"class":218},[208,775,776,779,782],{"class":210,"line":296},[208,777,778],{"class":218},"  maxTokenAge: ",[208,780,781],{"class":225},"\"24h\"",[208,783,763],{"class":218},[208,785,786],{"class":210,"line":325},[208,787,788],{"class":218},"});\n",[178,790,791,792,798],{},"See ",[187,793,795],{"href":794},"\u002Futilities#validatejwtclaims",[205,796,797],{},"validateJwtClaims"," for the full option list.",[800,801,802],"note",{},[178,803,804,805,811,812,814,815,817,818,821],{},"Claim validation runs ",[182,806,807,808,810],{},"independently of the ",[205,809,721],{}," header",", because ",[205,813,721],{}," is attacker-controlled. A token whose payload happens to look like JWT claims gets validated even if ",[205,816,721],{}," is absent or exotic. Pass ",[205,819,820],{},"validateClaims: false"," only when you've genuinely chosen JWS-for-arbitrary-bytes semantics.",[252,823,825],{"id":824},"going-further","Going further",[543,827,828,846,852,858],{},[546,829,830,833,834,837,838,841,842,845],{},[187,831,832],{"href":38},"Signing in depth →"," — every option on ",[205,835,836],{},"sign()",", including ",[205,839,840],{},"b64: false"," (unencoded payload) and ",[205,843,844],{},"kid"," fallback.",[546,847,848,851],{},[187,849,850],{"href":42},"Verifying in depth →"," — JWKSet retry, dynamic key lookup, allowlist inference, advanced claim options.",[546,853,854,857],{},[187,855,856],{"href":46},"Multi-signature →"," — more than one signer on a single payload (JSON Serialization).",[546,859,860,863],{},[187,861,862],{"href":50},"Algorithms →"," — picking between HMAC, RSA-PSS, ECDSA, EdDSA.",[865,866,867],"style",{},"html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}",{"title":203,"searchDepth":232,"depth":232,"links":869},[870,871,872,873,874],{"id":254,"depth":232,"text":255},{"id":435,"depth":232,"text":436},{"id":537,"depth":232,"text":538},{"id":678,"depth":232,"text":679},{"id":824,"depth":232,"text":825},"md",{"icon":35},{"icon":35},{"title":30,"description":203},"II--jK1-XhtUkb2ciMOH3RbKOpLX-61G8WKJof1sZj0",[881,882],{"title":23,"path":24,"stem":25,"description":203,"icon":28,"children":-1},{"title":37,"path":38,"stem":39,"description":203,"children":-1},1776888560029]