[{"data":1,"prerenderedAt":1322},["ShallowReactive",2],{"navigation":3,"-examples-authentication-basics":167,"-examples-authentication-basics-surround":1319},[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":86,"body":169,"description":201,"extension":1314,"meta":1315,"navigation":1316,"path":87,"seo":1317,"stem":88,"__hash__":1318},"content\u002F10.Examples\u002F1.authentication-basics.md",{"type":170,"value":171,"toc":1304,"icon":89},"minimark",[172,176,189,194,282,294,423,427,539,542,576,579,609,613,960,963,1030,1034,1044,1114,1128,1138,1141,1231,1242,1246,1280,1284,1300],[173,174,175],"p",{},"A minimal auth flow: sign a token on login, verify it on every protected request, reject expired or tampered tokens automatically.",[173,177,178,179,183,184,188],{},"This example uses ",[180,181,182],"code",{},"HS256"," (symmetric) — the simplest and fastest path when the same service signs and verifies. If you're building a system where third parties need to verify tokens without being able to sign them, see ",[185,186,187],"a",{"href":92},"JWKS endpoint"," instead.",[190,191,193],"h2",{"id":192},"setup","Setup",[195,196,202],"pre",{"className":197,"code":198,"filename":199,"language":200,"meta":201,"style":201},"language-ts shiki shiki-themes github-light github-dark github-dark","import { generateJWK } from \"unjwt\u002Fjwk\";\n\n\u002F\u002F Generate once, persist. Treat like any other secret.\nconst signingKey = await generateJWK(\"HS256\", { kid: \"primary-2025\" });\n\u002F\u002F { kty: \"oct\", k: \"...\", alg: \"HS256\", kid: \"primary-2025\" }\n","secrets.ts","ts","",[180,203,204,227,234,241,276],{"__ignoreMap":201},[205,206,209,213,217,220,224],"span",{"class":207,"line":208},"line",1,[205,210,212],{"class":211},"so5gQ","import",[205,214,216],{"class":215},"slsVL"," { generateJWK } ",[205,218,219],{"class":211},"from",[205,221,223],{"class":222},"sfrk1"," \"unjwt\u002Fjwk\"",[205,225,226],{"class":215},";\n",[205,228,230],{"class":207,"line":229},2,[205,231,233],{"emptyLinePlaceholder":232},true,"\n",[205,235,237],{"class":207,"line":236},3,[205,238,240],{"class":239},"sCsY4","\u002F\u002F Generate once, persist. Treat like any other secret.\n",[205,242,244,247,251,254,257,261,264,267,270,273],{"class":207,"line":243},4,[205,245,246],{"class":211},"const",[205,248,250],{"class":249},"suiK_"," signingKey",[205,252,253],{"class":211}," =",[205,255,256],{"class":211}," await",[205,258,260],{"class":259},"shcOC"," generateJWK",[205,262,263],{"class":215},"(",[205,265,266],{"class":222},"\"HS256\"",[205,268,269],{"class":215},", { kid: ",[205,271,272],{"class":222},"\"primary-2025\"",[205,274,275],{"class":215}," });\n",[205,277,279],{"class":207,"line":278},5,[205,280,281],{"class":239},"\u002F\u002F { kty: \"oct\", k: \"...\", alg: \"HS256\", kid: \"primary-2025\" }\n",[173,283,284,285,288,289,293],{},"In production, load this key from a secrets manager (or at minimum, ",[180,286,287],{},"process.env","). Do ",[290,291,292],"strong",{},"not"," generate a fresh key on every boot — all existing tokens would instantly become invalid.",[195,295,298],{"className":197,"code":296,"filename":297,"language":200,"meta":201,"style":201},"import type { JWK_oct } from \"unjwt\u002Fjwk\";\nimport { isSymmetricJWK } from \"unjwt\u002Futils\";\n\nconst parsed = JSON.parse(process.env.JWT_SIGNING_KEY!);\nif (!isSymmetricJWK(parsed)) {\n  throw new Error(\"JWT_SIGNING_KEY must be a symmetric JWK\");\n}\nconst signingKey: JWK_oct = parsed;\n","env-loading.ts",[180,299,300,316,330,334,364,380,399,405],{"__ignoreMap":201},[205,301,302,304,307,310,312,314],{"class":207,"line":208},[205,303,212],{"class":211},[205,305,306],{"class":211}," type",[205,308,309],{"class":215}," { JWK_oct } ",[205,311,219],{"class":211},[205,313,223],{"class":222},[205,315,226],{"class":215},[205,317,318,320,323,325,328],{"class":207,"line":229},[205,319,212],{"class":211},[205,321,322],{"class":215}," { isSymmetricJWK } ",[205,324,219],{"class":211},[205,326,327],{"class":222}," \"unjwt\u002Futils\"",[205,329,226],{"class":215},[205,331,332],{"class":207,"line":236},[205,333,233],{"emptyLinePlaceholder":232},[205,335,336,338,341,343,346,349,352,355,358,361],{"class":207,"line":243},[205,337,246],{"class":211},[205,339,340],{"class":249}," parsed",[205,342,253],{"class":211},[205,344,345],{"class":249}," JSON",[205,347,348],{"class":215},".",[205,350,351],{"class":259},"parse",[205,353,354],{"class":215},"(process.env.",[205,356,357],{"class":249},"JWT_SIGNING_KEY",[205,359,360],{"class":211},"!",[205,362,363],{"class":215},");\n",[205,365,366,369,372,374,377],{"class":207,"line":278},[205,367,368],{"class":211},"if",[205,370,371],{"class":215}," (",[205,373,360],{"class":211},[205,375,376],{"class":259},"isSymmetricJWK",[205,378,379],{"class":215},"(parsed)) {\n",[205,381,383,386,389,392,394,397],{"class":207,"line":382},6,[205,384,385],{"class":211},"  throw",[205,387,388],{"class":211}," new",[205,390,391],{"class":259}," Error",[205,393,263],{"class":215},[205,395,396],{"class":222},"\"JWT_SIGNING_KEY must be a symmetric JWK\"",[205,398,363],{"class":215},[205,400,402],{"class":207,"line":401},7,[205,403,404],{"class":215},"}\n",[205,406,408,410,412,415,418,420],{"class":207,"line":407},8,[205,409,246],{"class":211},[205,411,250],{"class":249},[205,413,414],{"class":211},":",[205,416,417],{"class":259}," JWK_oct",[205,419,253],{"class":211},[205,421,422],{"class":215}," parsed;\n",[190,424,426],{"id":425},"issue-a-token-login","Issue a token (login)",[195,428,431],{"className":197,"code":429,"filename":430,"language":200,"meta":201,"style":201},"import { sign } from \"unjwt\u002Fjws\";\n\nasync function issueAccessToken(userId: string, roles: string[]) {\n  return sign({ sub: userId, roles }, signingKey, {\n    expiresIn: \"15m\",\n    protectedHeader: {\n      typ: \"access+jwt\",\n    },\n  });\n}\n","login.ts",[180,432,433,447,451,486,497,508,513,523,528,534],{"__ignoreMap":201},[205,434,435,437,440,442,445],{"class":207,"line":208},[205,436,212],{"class":211},[205,438,439],{"class":215}," { sign } ",[205,441,219],{"class":211},[205,443,444],{"class":222}," \"unjwt\u002Fjws\"",[205,446,226],{"class":215},[205,448,449],{"class":207,"line":229},[205,450,233],{"emptyLinePlaceholder":232},[205,452,453,456,459,462,464,468,470,473,476,479,481,483],{"class":207,"line":236},[205,454,455],{"class":211},"async",[205,457,458],{"class":211}," function",[205,460,461],{"class":259}," issueAccessToken",[205,463,263],{"class":215},[205,465,467],{"class":466},"sQHwn","userId",[205,469,414],{"class":211},[205,471,472],{"class":249}," string",[205,474,475],{"class":215},", ",[205,477,478],{"class":466},"roles",[205,480,414],{"class":211},[205,482,472],{"class":249},[205,484,485],{"class":215},"[]) {\n",[205,487,488,491,494],{"class":207,"line":243},[205,489,490],{"class":211},"  return",[205,492,493],{"class":259}," sign",[205,495,496],{"class":215},"({ sub: userId, roles }, signingKey, {\n",[205,498,499,502,505],{"class":207,"line":278},[205,500,501],{"class":215},"    expiresIn: ",[205,503,504],{"class":222},"\"15m\"",[205,506,507],{"class":215},",\n",[205,509,510],{"class":207,"line":382},[205,511,512],{"class":215},"    protectedHeader: {\n",[205,514,515,518,521],{"class":207,"line":401},[205,516,517],{"class":215},"      typ: ",[205,519,520],{"class":222},"\"access+jwt\"",[205,522,507],{"class":215},[205,524,525],{"class":207,"line":407},[205,526,527],{"class":215},"    },\n",[205,529,531],{"class":207,"line":530},9,[205,532,533],{"class":215},"  });\n",[205,535,537],{"class":207,"line":536},10,[205,538,404],{"class":215},[173,540,541],{},"What unjwt adds to the payload automatically:",[543,544,545,552,566],"ul",{},[546,547,548,551],"li",{},[180,549,550],{},"iat"," — current time.",[546,553,554,557,558,561,562,565],{},[180,555,556],{},"exp"," — ",[180,559,560],{},"iat + 900"," (15 minutes from ",[180,563,564],{},"expiresIn",").",[546,567,568,571,572,575],{},[180,569,570],{},"kid"," — copied from ",[180,573,574],{},"signingKey.kid"," into the header.",[173,577,578],{},"What your code controls:",[543,580,581,587,596],{},[546,582,583,586],{},[180,584,585],{},"sub"," — subject, typically the user id.",[546,588,589,591,592,595],{},[180,590,478],{}," \u002F ",[180,593,594],{},"scopes"," \u002F whatever else your app needs.",[546,597,598,601,602,608],{},[180,599,600],{},"typ: \"access+jwt\""," — a distinct type so a refresh token from the same app can't be mistaken for an access token (",[185,603,607],{"href":604,"rel":605},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc8725#section-3.11",[606],"nofollow","RFC 8725 §3.11"," recommendation).",[190,610,612],{"id":611},"verify-on-a-protected-route","Verify on a protected route",[195,614,617],{"className":197,"code":615,"filename":616,"language":200,"meta":201,"style":201},"import { verify, type JWSVerifyResult } from \"unjwt\u002Fjws\";\n\nasync function requireAuth(\n  request: Request,\n): Promise\u003CJWSVerifyResult\u003C{ sub: string; roles: string[] }>> {\n  const authHeader = request.headers.get(\"authorization\");\n  if (!authHeader?.startsWith(\"Bearer \")) {\n    throw new Response(\"Unauthorized\", { status: 401 });\n  }\n  const token = authHeader.slice(\"Bearer \".length);\n\n  try {\n    return await verify\u003C{ sub: string; roles: string[] }>(token, signingKey, {\n      audience: \"my-api\",\n      issuer: \"my-app\",\n      typ: \"access+jwt\", \u002F\u002F must match what we set on issue\n      maxTokenAge: \"15m\", \u002F\u002F defense-in-depth on top of exp\n    });\n  } catch (error) {\n    throw new Response(`Invalid token: ${(error as Error).message}`, { status: 401 });\n  }\n}\n","middleware.ts",[180,618,619,638,642,654,666,703,726,749,772,777,803,808,817,847,858,869,881,894,900,912,950,955],{"__ignoreMap":201},[205,620,621,623,626,629,632,634,636],{"class":207,"line":208},[205,622,212],{"class":211},[205,624,625],{"class":215}," { verify, ",[205,627,628],{"class":211},"type",[205,630,631],{"class":215}," JWSVerifyResult } ",[205,633,219],{"class":211},[205,635,444],{"class":222},[205,637,226],{"class":215},[205,639,640],{"class":207,"line":229},[205,641,233],{"emptyLinePlaceholder":232},[205,643,644,646,648,651],{"class":207,"line":236},[205,645,455],{"class":211},[205,647,458],{"class":211},[205,649,650],{"class":259}," requireAuth",[205,652,653],{"class":215},"(\n",[205,655,656,659,661,664],{"class":207,"line":243},[205,657,658],{"class":466},"  request",[205,660,414],{"class":211},[205,662,663],{"class":259}," Request",[205,665,507],{"class":215},[205,667,668,671,673,676,679,682,685,687,689,691,694,696,698,700],{"class":207,"line":278},[205,669,670],{"class":215},")",[205,672,414],{"class":211},[205,674,675],{"class":259}," Promise",[205,677,678],{"class":215},"\u003C",[205,680,681],{"class":259},"JWSVerifyResult",[205,683,684],{"class":215},"\u003C{ ",[205,686,585],{"class":466},[205,688,414],{"class":211},[205,690,472],{"class":249},[205,692,693],{"class":215},"; ",[205,695,478],{"class":466},[205,697,414],{"class":211},[205,699,472],{"class":249},[205,701,702],{"class":215},"[] }>> {\n",[205,704,705,708,711,713,716,719,721,724],{"class":207,"line":382},[205,706,707],{"class":211},"  const",[205,709,710],{"class":249}," authHeader",[205,712,253],{"class":211},[205,714,715],{"class":215}," request.headers.",[205,717,718],{"class":259},"get",[205,720,263],{"class":215},[205,722,723],{"class":222},"\"authorization\"",[205,725,363],{"class":215},[205,727,728,731,733,735,738,741,743,746],{"class":207,"line":401},[205,729,730],{"class":211},"  if",[205,732,371],{"class":215},[205,734,360],{"class":211},[205,736,737],{"class":215},"authHeader?.",[205,739,740],{"class":259},"startsWith",[205,742,263],{"class":215},[205,744,745],{"class":222},"\"Bearer \"",[205,747,748],{"class":215},")) {\n",[205,750,751,754,756,759,761,764,767,770],{"class":207,"line":407},[205,752,753],{"class":211},"    throw",[205,755,388],{"class":211},[205,757,758],{"class":259}," Response",[205,760,263],{"class":215},[205,762,763],{"class":222},"\"Unauthorized\"",[205,765,766],{"class":215},", { status: ",[205,768,769],{"class":249},"401",[205,771,275],{"class":215},[205,773,774],{"class":207,"line":530},[205,775,776],{"class":215},"  }\n",[205,778,779,781,784,786,789,792,794,796,798,801],{"class":207,"line":536},[205,780,707],{"class":211},[205,782,783],{"class":249}," token",[205,785,253],{"class":211},[205,787,788],{"class":215}," authHeader.",[205,790,791],{"class":259},"slice",[205,793,263],{"class":215},[205,795,745],{"class":222},[205,797,348],{"class":215},[205,799,800],{"class":249},"length",[205,802,363],{"class":215},[205,804,806],{"class":207,"line":805},11,[205,807,233],{"emptyLinePlaceholder":232},[205,809,811,814],{"class":207,"line":810},12,[205,812,813],{"class":211},"  try",[205,815,816],{"class":215}," {\n",[205,818,820,823,825,828,830,832,834,836,838,840,842,844],{"class":207,"line":819},13,[205,821,822],{"class":211},"    return",[205,824,256],{"class":211},[205,826,827],{"class":259}," verify",[205,829,684],{"class":215},[205,831,585],{"class":466},[205,833,414],{"class":211},[205,835,472],{"class":249},[205,837,693],{"class":215},[205,839,478],{"class":466},[205,841,414],{"class":211},[205,843,472],{"class":249},[205,845,846],{"class":215},"[] }>(token, signingKey, {\n",[205,848,850,853,856],{"class":207,"line":849},14,[205,851,852],{"class":215},"      audience: ",[205,854,855],{"class":222},"\"my-api\"",[205,857,507],{"class":215},[205,859,861,864,867],{"class":207,"line":860},15,[205,862,863],{"class":215},"      issuer: ",[205,865,866],{"class":222},"\"my-app\"",[205,868,507],{"class":215},[205,870,872,874,876,878],{"class":207,"line":871},16,[205,873,517],{"class":215},[205,875,520],{"class":222},[205,877,475],{"class":215},[205,879,880],{"class":239},"\u002F\u002F must match what we set on issue\n",[205,882,884,887,889,891],{"class":207,"line":883},17,[205,885,886],{"class":215},"      maxTokenAge: ",[205,888,504],{"class":222},[205,890,475],{"class":215},[205,892,893],{"class":239},"\u002F\u002F defense-in-depth on top of exp\n",[205,895,897],{"class":207,"line":896},18,[205,898,899],{"class":215},"    });\n",[205,901,903,906,909],{"class":207,"line":902},19,[205,904,905],{"class":215},"  } ",[205,907,908],{"class":211},"catch",[205,910,911],{"class":215}," (error) {\n",[205,913,915,917,919,921,923,926,928,931,934,936,938,941,944,946,948],{"class":207,"line":914},20,[205,916,753],{"class":211},[205,918,388],{"class":211},[205,920,758],{"class":259},[205,922,263],{"class":215},[205,924,925],{"class":222},"`Invalid token: ${",[205,927,263],{"class":222},[205,929,930],{"class":215},"error",[205,932,933],{"class":211}," as",[205,935,391],{"class":259},[205,937,565],{"class":222},[205,939,940],{"class":215},"message",[205,942,943],{"class":222},"}`",[205,945,766],{"class":215},[205,947,769],{"class":249},[205,949,275],{"class":215},[205,951,953],{"class":207,"line":952},21,[205,954,776],{"class":215},[205,956,958],{"class":207,"line":957},22,[205,959,404],{"class":215},[173,961,962],{},"Then in a route:",[195,964,967],{"className":197,"code":965,"filename":966,"language":200,"meta":201,"style":201},"async function getProfile(request: Request) {\n  const { payload } = await requireAuth(request);\n  return Response.json({ userId: payload.sub, roles: payload.roles });\n}\n","route.ts",[180,968,969,990,1013,1026],{"__ignoreMap":201},[205,970,971,973,975,978,980,983,985,987],{"class":207,"line":208},[205,972,455],{"class":211},[205,974,458],{"class":211},[205,976,977],{"class":259}," getProfile",[205,979,263],{"class":215},[205,981,982],{"class":466},"request",[205,984,414],{"class":211},[205,986,663],{"class":259},[205,988,989],{"class":215},") {\n",[205,991,992,994,997,1000,1003,1006,1008,1010],{"class":207,"line":229},[205,993,707],{"class":211},[205,995,996],{"class":215}," { ",[205,998,999],{"class":249},"payload",[205,1001,1002],{"class":215}," } ",[205,1004,1005],{"class":211},"=",[205,1007,256],{"class":211},[205,1009,650],{"class":259},[205,1011,1012],{"class":215},"(request);\n",[205,1014,1015,1017,1020,1023],{"class":207,"line":236},[205,1016,490],{"class":211},[205,1018,1019],{"class":215}," Response.",[205,1021,1022],{"class":259},"json",[205,1024,1025],{"class":215},"({ userId: payload.sub, roles: payload.roles });\n",[205,1027,1028],{"class":207,"line":243},[205,1029,404],{"class":215},[190,1031,1033],{"id":1032},"whats-checked-automatically","What's checked automatically",[173,1035,1036,1037,1040,1041,414],{},"When you call ",[180,1038,1039],{},"verify()"," with a payload that's a JSON object, unjwt runs these checks ",[290,1042,1043],{},"without needing you to opt in",[1045,1046,1047,1060],"table",{},[1048,1049,1050],"thead",{},[1051,1052,1053,1057],"tr",{},[1054,1055,1056],"th",{},"Check",[1054,1058,1059],{},"What it rejects",[1061,1062,1063,1072,1086,1095,1105],"tbody",{},[1051,1064,1065,1069],{},[1066,1067,1068],"td",{},"Signature",[1066,1070,1071],{},"Token was tampered with, or signed by the wrong key.",[1051,1073,1074,1080],{},[1066,1075,1076,1079],{},[180,1077,1078],{},"alg"," allowlist",[1066,1081,1082,1083,348],{},"Wrong algorithm — derived from ",[180,1084,1085],{},"signingKey.alg",[1051,1087,1088,1092],{},[1066,1089,1090],{},[180,1091,556],{},[1066,1093,1094],{},"Token is past its expiration.",[1051,1096,1097,1102],{},[1066,1098,1099],{},[180,1100,1101],{},"nbf",[1066,1103,1104],{},"Token is used before its \"not before\" time.",[1051,1106,1107,1111],{},[1066,1108,1109],{},[180,1110,550],{},[1066,1112,1113],{},"Not a finite number (if present).",[173,1115,1116,1117,1120,1121,1124,1125,1127],{},"These are the checks that protect you from the classic JWT pitfalls (",[180,1118,1119],{},"alg: none"," attack, algorithm confusion, expired-token reuse). You don't need to pass ",[180,1122,1123],{},"algorithms"," explicitly because ",[180,1126,1085],{}," is set.",[190,1129,1131,1132,591,1135],{"id":1130},"adding-issuer-audience","Adding ",[180,1133,1134],{},"issuer",[180,1136,1137],{},"audience",[173,1139,1140],{},"These are defence-in-depth. They catch bugs where the same key is accidentally reused across services:",[1142,1143,1144,1194],"CodeGroup",{},[195,1145,1148],{"className":197,"code":1146,"filename":1147,"language":200,"meta":201,"style":201},"await sign({ sub: userId, roles, iss: \"my-app\", aud: \"my-api\" }, signingKey, {\n  expiresIn: \"15m\",\n  protectedHeader: { typ: \"access+jwt\" },\n});\n","on-issue.ts",[180,1149,1150,1170,1179,1189],{"__ignoreMap":201},[205,1151,1152,1155,1157,1160,1162,1165,1167],{"class":207,"line":208},[205,1153,1154],{"class":211},"await",[205,1156,493],{"class":259},[205,1158,1159],{"class":215},"({ sub: userId, roles, iss: ",[205,1161,866],{"class":222},[205,1163,1164],{"class":215},", aud: ",[205,1166,855],{"class":222},[205,1168,1169],{"class":215}," }, signingKey, {\n",[205,1171,1172,1175,1177],{"class":207,"line":229},[205,1173,1174],{"class":215},"  expiresIn: ",[205,1176,504],{"class":222},[205,1178,507],{"class":215},[205,1180,1181,1184,1186],{"class":207,"line":236},[205,1182,1183],{"class":215},"  protectedHeader: { typ: ",[205,1185,520],{"class":222},[205,1187,1188],{"class":215}," },\n",[205,1190,1191],{"class":207,"line":243},[205,1192,1193],{"class":215},"});\n",[195,1195,1198],{"className":197,"code":1196,"filename":1197,"language":200,"meta":201,"style":201},"await verify(token, signingKey, {\n  issuer: \"my-app\",\n  audience: \"my-api\",\n});\n","on-verify.ts",[180,1199,1200,1209,1218,1227],{"__ignoreMap":201},[205,1201,1202,1204,1206],{"class":207,"line":208},[205,1203,1154],{"class":211},[205,1205,827],{"class":259},[205,1207,1208],{"class":215},"(token, signingKey, {\n",[205,1210,1211,1214,1216],{"class":207,"line":229},[205,1212,1213],{"class":215},"  issuer: ",[205,1215,866],{"class":222},[205,1217,507],{"class":215},[205,1219,1220,1223,1225],{"class":207,"line":236},[205,1221,1222],{"class":215},"  audience: ",[205,1224,855],{"class":222},[205,1226,507],{"class":215},[205,1228,1229],{"class":207,"line":243},[205,1230,1193],{"class":215},[173,1232,1233,1234,1237,1238,1241],{},"Now a token signed for ",[180,1235,1236],{},"my-api"," can't verify as a token for ",[180,1239,1240],{},"admin-api",", even if both use the same signing key during a shared-infrastructure transition.",[190,1243,1245],{"id":1244},"next-steps","Next steps",[543,1247,1248,1261,1271],{},[546,1249,1250,1253,1254,1257,1258,348],{},[290,1251,1252],{},"Third parties need to verify your tokens?"," Switch to an asymmetric algorithm (e.g. ",[180,1255,1256],{},"ES256",") and publish a ",[185,1259,1260],{"href":92},"JWKS endpoint →",[546,1262,1263,1266,1267,1270],{},[290,1264,1265],{},"Want automatic session handling?"," Use the ",[185,1268,1269],{"href":150},"H3 session adapters →"," — cookie-based sessions with built-in refresh hooks.",[546,1272,1273,1276,1277,348],{},[290,1274,1275],{},"Need long sessions without long-lived tokens?"," Go to ",[185,1278,1279],{"href":96},"Refresh token pattern →",[190,1281,1283],{"id":1282},"see-also","See also",[543,1285,1286,1291,1296],{},[546,1287,1288],{},[185,1289,1290],{"href":42},"JWS: verifying",[546,1292,1293],{},[185,1294,1295],{"href":38},"JWS: signing",[546,1297,1298],{},[185,1299,19],{"href":20},[1301,1302,1303],"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 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}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 .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"title":201,"searchDepth":229,"depth":229,"links":1305},[1306,1307,1308,1309,1310,1312,1313],{"id":192,"depth":229,"text":193},{"id":425,"depth":229,"text":426},{"id":611,"depth":229,"text":612},{"id":1032,"depth":229,"text":1033},{"id":1130,"depth":229,"text":1311},"Adding issuer \u002F audience",{"id":1244,"depth":229,"text":1245},{"id":1282,"depth":229,"text":1283},"md",{"icon":89},{"icon":89},{"title":86,"description":201},"ZQ5vOtvjFVXKtQzQkxtCwNmvp-J854yt-KIVeWfNIp4",[1320,1321],{"title":79,"path":80,"stem":81,"description":201,"icon":84,"children":-1},{"title":91,"path":92,"stem":93,"description":201,"icon":89,"children":-1},1776888559110]