[{"data":1,"prerenderedAt":1445},["ShallowReactive",2],{"navigation":3,"-examples-jwks-endpoint":167,"-examples-jwks-endpoint-surround":1442},[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":91,"body":169,"description":191,"extension":1437,"meta":1438,"navigation":1439,"path":92,"seo":1440,"stem":93,"__hash__":1441},"content\u002F10.Examples\u002F2.jwks-endpoint.md",{"type":170,"value":171,"toc":1429,"icon":89},"minimark",[172,181,192,199,204,373,386,398,402,405,793,799,969,973,976,996,999,1155,1169,1173,1337,1341,1347,1395,1401,1405,1425],[173,174,175,176,180],"p",{},"OAuth and OIDC providers (Auth0, Okta, Entra, Keycloak, GitHub, Google, …) publish their public keys as a ",[177,178,179],"strong",{},"JWKS"," — a JWK Set served from a well-known URL:",[182,183,188],"pre",{"className":184,"code":186,"language":187},[185],"language-text","https:\u002F\u002Fauth.example.com\u002F.well-known\u002Fjwks.json\n","text",[189,190,186],"code",{"__ignoreMap":191},"",[173,193,194,195,198],{},"Your service fetches that document, caches it, and uses it to verify tokens issued by the provider. Each token's header carries a ",[189,196,197],{},"kid"," that matches one of the keys in the set.",[200,201,203],"h2",{"id":202},"the-one-shot-version","The one-shot version",[182,205,210],{"className":206,"code":207,"filename":208,"language":209,"meta":191,"style":191},"language-ts shiki shiki-themes github-light github-dark github-dark","import { verify } from \"unjwt\u002Fjws\";\n\nconst jwks = await fetch(\"https:\u002F\u002Fauth.example.com\u002F.well-known\u002Fjwks.json\").then((r) => r.json());\n\nconst { payload } = await verify(tokenFromProvider, jwks, {\n  algorithms: [\"RS256\", \"ES256\"],\n  issuer: \"https:\u002F\u002Fauth.example.com\",\n  audience: \"my-api\",\n});\n","verify.ts","ts",[189,211,212,235,242,296,301,326,344,356,367],{"__ignoreMap":191},[213,214,217,221,225,228,232],"span",{"class":215,"line":216},"line",1,[213,218,220],{"class":219},"so5gQ","import",[213,222,224],{"class":223},"slsVL"," { verify } ",[213,226,227],{"class":219},"from",[213,229,231],{"class":230},"sfrk1"," \"unjwt\u002Fjws\"",[213,233,234],{"class":223},";\n",[213,236,238],{"class":215,"line":237},2,[213,239,241],{"emptyLinePlaceholder":240},true,"\n",[213,243,245,248,252,255,258,262,265,268,271,274,277,281,284,287,290,293],{"class":215,"line":244},3,[213,246,247],{"class":219},"const",[213,249,251],{"class":250},"suiK_"," jwks",[213,253,254],{"class":219}," =",[213,256,257],{"class":219}," await",[213,259,261],{"class":260},"shcOC"," fetch",[213,263,264],{"class":223},"(",[213,266,267],{"class":230},"\"https:\u002F\u002Fauth.example.com\u002F.well-known\u002Fjwks.json\"",[213,269,270],{"class":223},").",[213,272,273],{"class":260},"then",[213,275,276],{"class":223},"((",[213,278,280],{"class":279},"sQHwn","r",[213,282,283],{"class":223},") ",[213,285,286],{"class":219},"=>",[213,288,289],{"class":223}," r.",[213,291,292],{"class":260},"json",[213,294,295],{"class":223},"());\n",[213,297,299],{"class":215,"line":298},4,[213,300,241],{"emptyLinePlaceholder":240},[213,302,304,306,309,312,315,318,320,323],{"class":215,"line":303},5,[213,305,247],{"class":219},[213,307,308],{"class":223}," { ",[213,310,311],{"class":250},"payload",[213,313,314],{"class":223}," } ",[213,316,317],{"class":219},"=",[213,319,257],{"class":219},[213,321,322],{"class":260}," verify",[213,324,325],{"class":223},"(tokenFromProvider, jwks, {\n",[213,327,329,332,335,338,341],{"class":215,"line":328},6,[213,330,331],{"class":223},"  algorithms: [",[213,333,334],{"class":230},"\"RS256\"",[213,336,337],{"class":223},", ",[213,339,340],{"class":230},"\"ES256\"",[213,342,343],{"class":223},"],\n",[213,345,347,350,353],{"class":215,"line":346},7,[213,348,349],{"class":223},"  issuer: ",[213,351,352],{"class":230},"\"https:\u002F\u002Fauth.example.com\"",[213,354,355],{"class":223},",\n",[213,357,359,362,365],{"class":215,"line":358},8,[213,360,361],{"class":223},"  audience: ",[213,363,364],{"class":230},"\"my-api\"",[213,366,355],{"class":223},[213,368,370],{"class":215,"line":369},9,[213,371,372],{"class":223},"});\n",[173,374,375,376,378,379,381,382,385],{},"unjwt reads the token's ",[189,377,197],{},", finds the matching key in the set, and verifies. If ",[189,380,197],{}," is absent or doesn't match, it throws ",[189,383,384],{},"ERR_JWK_KEY_NOT_FOUND"," before any crypto runs.",[387,388,389],"warning",{},[173,390,391,397],{},[177,392,393,394],{},"Always pass ",[189,395,396],{},"algorithms"," when consuming third-party tokens. The library's inferred allowlist from a set of mixed-algorithm keys is broader than you probably want — being explicit stops a malicious issuer from downgrading to a weaker algorithm you'd otherwise accept.",[200,399,401],{"id":400},"the-production-version-cached-fetch","The production version — cached fetch",[173,403,404],{},"In a real service you'd cache the JWKS to avoid hitting the provider on every request:",[182,406,409],{"className":206,"code":407,"filename":408,"language":209,"meta":191,"style":191},"interface JWKSCacheEntry {\n  jwks: { keys: JWK[] };\n  fetchedAt: number;\n}\n\nconst jwksCache = new Map\u003Cstring, JWKSCacheEntry>();\nconst JWKS_TTL_MS = 60 * 60 * 1000; \u002F\u002F 1 hour\n\nasync function getJwks(issuer: string): Promise\u003C{ keys: JWK[] }> {\n  const cached = jwksCache.get(issuer);\n  if (cached && Date.now() - cached.fetchedAt \u003C JWKS_TTL_MS) {\n    return cached.jwks;\n  }\n\n  const url = new URL(\"\u002F.well-known\u002Fjwks.json\", issuer).toString();\n  const jwks = await fetch(url).then((r) => {\n    if (!r.ok) throw new Error(`JWKS fetch failed: ${r.status}`);\n    return r.json();\n  });\n\n  jwksCache.set(issuer, { jwks, fetchedAt: Date.now() });\n  return jwks;\n}\n","jwks-cache.ts",[189,410,411,422,443,455,460,464,493,522,526,567,587,621,630,636,641,670,698,740,751,757,762,779,788],{"__ignoreMap":191},[213,412,413,416,419],{"class":215,"line":216},[213,414,415],{"class":219},"interface",[213,417,418],{"class":260}," JWKSCacheEntry",[213,420,421],{"class":223}," {\n",[213,423,424,427,430,432,435,437,440],{"class":215,"line":237},[213,425,426],{"class":279},"  jwks",[213,428,429],{"class":219},":",[213,431,308],{"class":223},[213,433,434],{"class":279},"keys",[213,436,429],{"class":219},[213,438,439],{"class":260}," JWK",[213,441,442],{"class":223},"[] };\n",[213,444,445,448,450,453],{"class":215,"line":244},[213,446,447],{"class":279},"  fetchedAt",[213,449,429],{"class":219},[213,451,452],{"class":250}," number",[213,454,234],{"class":223},[213,456,457],{"class":215,"line":298},[213,458,459],{"class":223},"}\n",[213,461,462],{"class":215,"line":303},[213,463,241],{"emptyLinePlaceholder":240},[213,465,466,468,471,473,476,479,482,485,487,490],{"class":215,"line":328},[213,467,247],{"class":219},[213,469,470],{"class":250}," jwksCache",[213,472,254],{"class":219},[213,474,475],{"class":219}," new",[213,477,478],{"class":260}," Map",[213,480,481],{"class":223},"\u003C",[213,483,484],{"class":250},"string",[213,486,337],{"class":223},[213,488,489],{"class":260},"JWKSCacheEntry",[213,491,492],{"class":223},">();\n",[213,494,495,497,500,502,505,508,510,512,515,518],{"class":215,"line":346},[213,496,247],{"class":219},[213,498,499],{"class":250}," JWKS_TTL_MS",[213,501,254],{"class":219},[213,503,504],{"class":250}," 60",[213,506,507],{"class":219}," *",[213,509,504],{"class":250},[213,511,507],{"class":219},[213,513,514],{"class":250}," 1000",[213,516,517],{"class":223},"; ",[213,519,521],{"class":520},"sCsY4","\u002F\u002F 1 hour\n",[213,523,524],{"class":215,"line":358},[213,525,241],{"emptyLinePlaceholder":240},[213,527,528,531,534,537,539,542,544,547,550,552,555,558,560,562,564],{"class":215,"line":369},[213,529,530],{"class":219},"async",[213,532,533],{"class":219}," function",[213,535,536],{"class":260}," getJwks",[213,538,264],{"class":223},[213,540,541],{"class":279},"issuer",[213,543,429],{"class":219},[213,545,546],{"class":250}," string",[213,548,549],{"class":223},")",[213,551,429],{"class":219},[213,553,554],{"class":260}," Promise",[213,556,557],{"class":223},"\u003C{ ",[213,559,434],{"class":279},[213,561,429],{"class":219},[213,563,439],{"class":260},[213,565,566],{"class":223},"[] }> {\n",[213,568,570,573,576,578,581,584],{"class":215,"line":569},10,[213,571,572],{"class":219},"  const",[213,574,575],{"class":250}," cached",[213,577,254],{"class":219},[213,579,580],{"class":223}," jwksCache.",[213,582,583],{"class":260},"get",[213,585,586],{"class":223},"(issuer);\n",[213,588,590,593,596,599,602,605,608,611,614,616,618],{"class":215,"line":589},11,[213,591,592],{"class":219},"  if",[213,594,595],{"class":223}," (cached ",[213,597,598],{"class":219},"&&",[213,600,601],{"class":223}," Date.",[213,603,604],{"class":260},"now",[213,606,607],{"class":223},"() ",[213,609,610],{"class":219},"-",[213,612,613],{"class":223}," cached.fetchedAt ",[213,615,481],{"class":219},[213,617,499],{"class":250},[213,619,620],{"class":223},") {\n",[213,622,624,627],{"class":215,"line":623},12,[213,625,626],{"class":219},"    return",[213,628,629],{"class":223}," cached.jwks;\n",[213,631,633],{"class":215,"line":632},13,[213,634,635],{"class":223},"  }\n",[213,637,639],{"class":215,"line":638},14,[213,640,241],{"emptyLinePlaceholder":240},[213,642,644,646,649,651,653,656,658,661,664,667],{"class":215,"line":643},15,[213,645,572],{"class":219},[213,647,648],{"class":250}," url",[213,650,254],{"class":219},[213,652,475],{"class":219},[213,654,655],{"class":260}," URL",[213,657,264],{"class":223},[213,659,660],{"class":230},"\"\u002F.well-known\u002Fjwks.json\"",[213,662,663],{"class":223},", issuer).",[213,665,666],{"class":260},"toString",[213,668,669],{"class":223},"();\n",[213,671,673,675,677,679,681,683,686,688,690,692,694,696],{"class":215,"line":672},16,[213,674,572],{"class":219},[213,676,251],{"class":250},[213,678,254],{"class":219},[213,680,257],{"class":219},[213,682,261],{"class":260},[213,684,685],{"class":223},"(url).",[213,687,273],{"class":260},[213,689,276],{"class":223},[213,691,280],{"class":279},[213,693,283],{"class":223},[213,695,286],{"class":219},[213,697,421],{"class":223},[213,699,701,704,707,710,713,716,718,721,723,726,728,731,734,737],{"class":215,"line":700},17,[213,702,703],{"class":219},"    if",[213,705,706],{"class":223}," (",[213,708,709],{"class":219},"!",[213,711,712],{"class":223},"r.ok) ",[213,714,715],{"class":219},"throw",[213,717,475],{"class":219},[213,719,720],{"class":260}," Error",[213,722,264],{"class":223},[213,724,725],{"class":230},"`JWKS fetch failed: ${",[213,727,280],{"class":223},[213,729,730],{"class":230},".",[213,732,733],{"class":223},"status",[213,735,736],{"class":230},"}`",[213,738,739],{"class":223},");\n",[213,741,743,745,747,749],{"class":215,"line":742},18,[213,744,626],{"class":219},[213,746,289],{"class":223},[213,748,292],{"class":260},[213,750,669],{"class":223},[213,752,754],{"class":215,"line":753},19,[213,755,756],{"class":223},"  });\n",[213,758,760],{"class":215,"line":759},20,[213,761,241],{"emptyLinePlaceholder":240},[213,763,765,768,771,774,776],{"class":215,"line":764},21,[213,766,767],{"class":223},"  jwksCache.",[213,769,770],{"class":260},"set",[213,772,773],{"class":223},"(issuer, { jwks, fetchedAt: Date.",[213,775,604],{"class":260},[213,777,778],{"class":223},"() });\n",[213,780,782,785],{"class":215,"line":781},22,[213,783,784],{"class":219},"  return",[213,786,787],{"class":223}," jwks;\n",[213,789,791],{"class":215,"line":790},23,[213,792,459],{"class":223},[173,794,795,796,798],{},"Then verify via a lookup function so unjwt only pays the fetch cost when the ",[189,797,197],{}," isn't already cached:",[182,800,803],{"className":206,"code":801,"filename":802,"language":209,"meta":191,"style":191},"import { verify } from \"unjwt\u002Fjws\";\nimport type { JWKLookupFunction } from \"unjwt\";\n\nconst ISSUER = \"https:\u002F\u002Fauth.example.com\";\n\nconst lookup: JWKLookupFunction = async (header) => {\n  const jwks = await getJwks(ISSUER);\n  return jwks; \u002F\u002F unjwt picks the matching kid from the set\n};\n\nconst { payload } = await verify(tokenFromProvider, lookup, {\n  algorithms: [\"RS256\"],\n  issuer: ISSUER,\n  audience: \"my-api\",\n});\n","verify-with-cache.ts",[189,804,805,817,834,838,852,856,884,903,913,918,922,941,949,957,965],{"__ignoreMap":191},[213,806,807,809,811,813,815],{"class":215,"line":216},[213,808,220],{"class":219},[213,810,224],{"class":223},[213,812,227],{"class":219},[213,814,231],{"class":230},[213,816,234],{"class":223},[213,818,819,821,824,827,829,832],{"class":215,"line":237},[213,820,220],{"class":219},[213,822,823],{"class":219}," type",[213,825,826],{"class":223}," { JWKLookupFunction } ",[213,828,227],{"class":219},[213,830,831],{"class":230}," \"unjwt\"",[213,833,234],{"class":223},[213,835,836],{"class":215,"line":244},[213,837,241],{"emptyLinePlaceholder":240},[213,839,840,842,845,847,850],{"class":215,"line":298},[213,841,247],{"class":219},[213,843,844],{"class":250}," ISSUER",[213,846,254],{"class":219},[213,848,849],{"class":230}," \"https:\u002F\u002Fauth.example.com\"",[213,851,234],{"class":223},[213,853,854],{"class":215,"line":303},[213,855,241],{"emptyLinePlaceholder":240},[213,857,858,860,863,865,868,870,873,875,878,880,882],{"class":215,"line":328},[213,859,247],{"class":219},[213,861,862],{"class":260}," lookup",[213,864,429],{"class":219},[213,866,867],{"class":260}," JWKLookupFunction",[213,869,254],{"class":219},[213,871,872],{"class":219}," async",[213,874,706],{"class":223},[213,876,877],{"class":279},"header",[213,879,283],{"class":223},[213,881,286],{"class":219},[213,883,421],{"class":223},[213,885,886,888,890,892,894,896,898,901],{"class":215,"line":346},[213,887,572],{"class":219},[213,889,251],{"class":250},[213,891,254],{"class":219},[213,893,257],{"class":219},[213,895,536],{"class":260},[213,897,264],{"class":223},[213,899,900],{"class":250},"ISSUER",[213,902,739],{"class":223},[213,904,905,907,910],{"class":215,"line":358},[213,906,784],{"class":219},[213,908,909],{"class":223}," jwks; ",[213,911,912],{"class":520},"\u002F\u002F unjwt picks the matching kid from the set\n",[213,914,915],{"class":215,"line":369},[213,916,917],{"class":223},"};\n",[213,919,920],{"class":215,"line":569},[213,921,241],{"emptyLinePlaceholder":240},[213,923,924,926,928,930,932,934,936,938],{"class":215,"line":589},[213,925,247],{"class":219},[213,927,308],{"class":223},[213,929,311],{"class":250},[213,931,314],{"class":223},[213,933,317],{"class":219},[213,935,257],{"class":219},[213,937,322],{"class":260},[213,939,940],{"class":223},"(tokenFromProvider, lookup, {\n",[213,942,943,945,947],{"class":215,"line":623},[213,944,331],{"class":223},[213,946,334],{"class":230},[213,948,343],{"class":223},[213,950,951,953,955],{"class":215,"line":632},[213,952,349],{"class":223},[213,954,900],{"class":250},[213,956,355],{"class":223},[213,958,959,961,963],{"class":215,"line":638},[213,960,361],{"class":223},[213,962,364],{"class":230},[213,964,355],{"class":223},[213,966,967],{"class":215,"line":643},[213,968,372],{"class":223},[200,970,972],{"id":971},"handling-key-rotation","Handling key rotation",[173,974,975],{},"When the provider rotates keys, the new key gets added to the JWKS and the old one sticks around for a grace period. Your service should:",[977,978,980,990],"steps",{"level":979},"4",[981,982,983,989],"h4",{},[177,984,985,986,988],{},"Refresh the cache when an unknown ",[189,987,197],{}," is seen"," — don't wait for the TTL.",[981,991,992,995],{},[177,993,994],{},"Keep serving requests"," during the refresh; old tokens may still be in flight.",[173,997,998],{},"A pattern for on-demand refresh:",[182,1000,1003],{"className":206,"code":1001,"filename":1002,"language":209,"meta":191,"style":191},"const lookup: JWKLookupFunction = async (header) => {\n  let jwks = await getJwks(ISSUER);\n\n  \u002F\u002F Fast path: kid is already in the cached set\n  if (header.kid && jwks.keys.some((k: any) => k.kid === header.kid)) {\n    return jwks;\n  }\n\n  \u002F\u002F Unknown kid — bypass cache and re-fetch once\n  jwksCache.delete(ISSUER);\n  jwks = await getJwks(ISSUER);\n  return jwks;\n};\n","jwks-smart-cache.ts",[189,1004,1005,1029,1049,1053,1058,1096,1102,1106,1110,1115,1128,1145,1151],{"__ignoreMap":191},[213,1006,1007,1009,1011,1013,1015,1017,1019,1021,1023,1025,1027],{"class":215,"line":216},[213,1008,247],{"class":219},[213,1010,862],{"class":260},[213,1012,429],{"class":219},[213,1014,867],{"class":260},[213,1016,254],{"class":219},[213,1018,872],{"class":219},[213,1020,706],{"class":223},[213,1022,877],{"class":279},[213,1024,283],{"class":223},[213,1026,286],{"class":219},[213,1028,421],{"class":223},[213,1030,1031,1034,1037,1039,1041,1043,1045,1047],{"class":215,"line":237},[213,1032,1033],{"class":219},"  let",[213,1035,1036],{"class":223}," jwks ",[213,1038,317],{"class":219},[213,1040,257],{"class":219},[213,1042,536],{"class":260},[213,1044,264],{"class":223},[213,1046,900],{"class":250},[213,1048,739],{"class":223},[213,1050,1051],{"class":215,"line":244},[213,1052,241],{"emptyLinePlaceholder":240},[213,1054,1055],{"class":215,"line":298},[213,1056,1057],{"class":520},"  \u002F\u002F Fast path: kid is already in the cached set\n",[213,1059,1060,1062,1065,1067,1070,1073,1075,1078,1080,1083,1085,1087,1090,1093],{"class":215,"line":303},[213,1061,592],{"class":219},[213,1063,1064],{"class":223}," (header.kid ",[213,1066,598],{"class":219},[213,1068,1069],{"class":223}," jwks.keys.",[213,1071,1072],{"class":260},"some",[213,1074,276],{"class":223},[213,1076,1077],{"class":279},"k",[213,1079,429],{"class":219},[213,1081,1082],{"class":250}," any",[213,1084,283],{"class":223},[213,1086,286],{"class":219},[213,1088,1089],{"class":223}," k.kid ",[213,1091,1092],{"class":219},"===",[213,1094,1095],{"class":223}," header.kid)) {\n",[213,1097,1098,1100],{"class":215,"line":328},[213,1099,626],{"class":219},[213,1101,787],{"class":223},[213,1103,1104],{"class":215,"line":346},[213,1105,635],{"class":223},[213,1107,1108],{"class":215,"line":358},[213,1109,241],{"emptyLinePlaceholder":240},[213,1111,1112],{"class":215,"line":369},[213,1113,1114],{"class":520},"  \u002F\u002F Unknown kid — bypass cache and re-fetch once\n",[213,1116,1117,1119,1122,1124,1126],{"class":215,"line":569},[213,1118,767],{"class":223},[213,1120,1121],{"class":260},"delete",[213,1123,264],{"class":223},[213,1125,900],{"class":250},[213,1127,739],{"class":223},[213,1129,1130,1133,1135,1137,1139,1141,1143],{"class":215,"line":589},[213,1131,1132],{"class":223},"  jwks ",[213,1134,317],{"class":219},[213,1136,257],{"class":219},[213,1138,536],{"class":260},[213,1140,264],{"class":223},[213,1142,900],{"class":250},[213,1144,739],{"class":223},[213,1146,1147,1149],{"class":215,"line":623},[213,1148,784],{"class":219},[213,1150,787],{"class":223},[213,1152,1153],{"class":215,"line":632},[213,1154,917],{"class":223},[173,1156,1157,1158,1162,1163,1165,1166,1168],{},"Be careful not to loop if the fetched JWKS ",[1159,1160,1161],"em",{},"still"," doesn't have the ",[189,1164,197],{}," — rate-limit the re-fetch (e.g. allow at most one per minute per issuer) to avoid a cache-busting attack where a malicious party sends tokens with random unknown ",[189,1167,197],{},"s.",[200,1170,1172],{"id":1171},"short-example-with-a-rate-limit","Short example with a rate-limit",[182,1174,1177],{"className":206,"code":1175,"filename":1176,"language":209,"meta":191,"style":191},"const LAST_REFRESH_FLOOR_MS = 60_000; \u002F\u002F don't re-fetch more than once\u002Fminute per issuer\nconst lastRefresh = new Map\u003Cstring, number>();\n\nasync function getJwksFresh(issuer: string) {\n  const now = Date.now();\n  const last = lastRefresh.get(issuer) ?? 0;\n  if (now - last > LAST_REFRESH_FLOOR_MS) {\n    jwksCache.delete(issuer);\n    lastRefresh.set(issuer, now);\n  }\n  return getJwks(issuer);\n}\n","rate-limited-refresh.ts",[189,1178,1179,1196,1220,1224,1243,1258,1283,1302,1311,1321,1325,1333],{"__ignoreMap":191},[213,1180,1181,1183,1186,1188,1191,1193],{"class":215,"line":216},[213,1182,247],{"class":219},[213,1184,1185],{"class":250}," LAST_REFRESH_FLOOR_MS",[213,1187,254],{"class":219},[213,1189,1190],{"class":250}," 60_000",[213,1192,517],{"class":223},[213,1194,1195],{"class":520},"\u002F\u002F don't re-fetch more than once\u002Fminute per issuer\n",[213,1197,1198,1200,1203,1205,1207,1209,1211,1213,1215,1218],{"class":215,"line":237},[213,1199,247],{"class":219},[213,1201,1202],{"class":250}," lastRefresh",[213,1204,254],{"class":219},[213,1206,475],{"class":219},[213,1208,478],{"class":260},[213,1210,481],{"class":223},[213,1212,484],{"class":250},[213,1214,337],{"class":223},[213,1216,1217],{"class":250},"number",[213,1219,492],{"class":223},[213,1221,1222],{"class":215,"line":244},[213,1223,241],{"emptyLinePlaceholder":240},[213,1225,1226,1228,1230,1233,1235,1237,1239,1241],{"class":215,"line":298},[213,1227,530],{"class":219},[213,1229,533],{"class":219},[213,1231,1232],{"class":260}," getJwksFresh",[213,1234,264],{"class":223},[213,1236,541],{"class":279},[213,1238,429],{"class":219},[213,1240,546],{"class":250},[213,1242,620],{"class":223},[213,1244,1245,1247,1250,1252,1254,1256],{"class":215,"line":303},[213,1246,572],{"class":219},[213,1248,1249],{"class":250}," now",[213,1251,254],{"class":219},[213,1253,601],{"class":223},[213,1255,604],{"class":260},[213,1257,669],{"class":223},[213,1259,1260,1262,1265,1267,1270,1272,1275,1278,1281],{"class":215,"line":328},[213,1261,572],{"class":219},[213,1263,1264],{"class":250}," last",[213,1266,254],{"class":219},[213,1268,1269],{"class":223}," lastRefresh.",[213,1271,583],{"class":260},[213,1273,1274],{"class":223},"(issuer) ",[213,1276,1277],{"class":219},"??",[213,1279,1280],{"class":250}," 0",[213,1282,234],{"class":223},[213,1284,1285,1287,1290,1292,1295,1298,1300],{"class":215,"line":346},[213,1286,592],{"class":219},[213,1288,1289],{"class":223}," (now ",[213,1291,610],{"class":219},[213,1293,1294],{"class":223}," last ",[213,1296,1297],{"class":219},">",[213,1299,1185],{"class":250},[213,1301,620],{"class":223},[213,1303,1304,1307,1309],{"class":215,"line":358},[213,1305,1306],{"class":223},"    jwksCache.",[213,1308,1121],{"class":260},[213,1310,586],{"class":223},[213,1312,1313,1316,1318],{"class":215,"line":369},[213,1314,1315],{"class":223},"    lastRefresh.",[213,1317,770],{"class":260},[213,1319,1320],{"class":223},"(issuer, now);\n",[213,1322,1323],{"class":215,"line":569},[213,1324,635],{"class":223},[213,1326,1327,1329,1331],{"class":215,"line":589},[213,1328,784],{"class":219},[213,1330,536],{"class":260},[213,1332,586],{"class":223},[213,1334,1335],{"class":215,"line":623},[213,1336,459],{"class":223},[200,1338,1340],{"id":1339},"what-exactly-unjwt-does","What exactly unjwt does",[173,1342,1343,1344,429],{},"With ",[189,1345,1346],{},"verify(token, jwks)",[977,1348,1349,1352,1357,1389,1392],{"level":979},[981,1350,1351],{},"Decodes and validates the protected header.",[981,1353,1354,1355,730],{},"Reads the token's ",[189,1356,197],{},[981,1358,1359,1360,1363,1364],{},"Looks up candidates in ",[189,1361,1362],{},"jwks.keys",":\n",[1365,1366,1367,1380],"ul",{},[1368,1369,1370,1371,1373,1374,1379],"li",{},"If ",[189,1372,197],{}," is present, ",[177,1375,1376,1377],{},"only keys with matching ",[189,1378,197],{}," are candidates (usually one).",[1368,1381,1370,1382,1384,1385,1388],{},[189,1383,197],{}," is absent, every key whose ",[189,1386,1387],{},"alg"," is compatible with the token is a candidate.",[981,1390,1391],{},"Verifies against the first candidate; falls back to the next on failure.",[981,1393,1394],{},"If no candidate verifies, throws.",[173,1396,1397,1398,1400],{},"No retry logic on your side, no ",[189,1399,197],{}," parsing on your side.",[200,1402,1404],{"id":1403},"see-also","See also",[1365,1406,1407,1413,1419],{},[1368,1408,1409],{},[1410,1411,1412],"a",{"href":135},"JWK Sets →",[1368,1414,1415],{},[1410,1416,1418],{"href":1417},"\u002Fjwt\u002Fjws\u002Fverifying#jwkset-automatic-key-rotation","JWS verifying → JWKSet automatic rotation",[1368,1420,1421,1424],{},[1410,1422,1423],{"href":87},"Authentication basics →"," — for the single-service case.",[1426,1427,1428],"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 .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 pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}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 .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":191,"searchDepth":237,"depth":237,"links":1430},[1431,1432,1433,1434,1435,1436],{"id":202,"depth":237,"text":203},{"id":400,"depth":237,"text":401},{"id":971,"depth":237,"text":972},{"id":1171,"depth":237,"text":1172},{"id":1339,"depth":237,"text":1340},{"id":1403,"depth":237,"text":1404},"md",{"icon":89},{"icon":89},{"title":91,"description":191},"J3FvO7XsYoX4h1ltCJR2z0FVDtUEaWARZeN0XxApv7k",[1443,1444],{"title":86,"path":87,"stem":88,"description":191,"icon":89,"children":-1},{"title":95,"path":96,"stem":97,"description":191,"icon":89,"children":-1},1776888559110]