[{"data":1,"prerenderedAt":1470},["ShallowReactive",2],{"docs:doc:\u002Fen\u002Fdocs\u002Fguides\u002Fquickstarts\u002Fnextjs":3,"docs:pages:docs_en":1400},{"id":4,"title":5,"alt":6,"body":7,"description":1391,"extension":1392,"key":1393,"meta":1394,"navigation":97,"order":525,"path":1395,"placeholder":1396,"seo":1397,"stem":1398,"__hash__":1399},"docs_en\u002Fen\u002Fdocs\u002Fguides\u002Fquickstarts\u002Fnextjs.md","Next.js","\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fnextjs",{"type":8,"value":9,"toc":1385},"minimark",[10,19,44,49,56,263,270,274,288,547,551,838,841,862,882,886,893,1257,1284,1308,1311,1381],[11,12,13,14,18],"p",{},"There is no Next.js package, and this page is why there does not need to be:\n",[15,16,17],"code",{},"@kleora-io\u002Fbrowser"," is framework-free, and the whole sign-in happens in\nthe browser. What follows is the App Router; the Pages Router differs only in\nwhere the two files live.",[20,21,26],"pre",{"className":22,"code":23,"language":24,"meta":25,"style":25},"language-bash shiki shiki-themes github-light","npm install @kleora-io\u002Fbrowser\n","bash","",[15,27,28],{"__ignoreMap":25},[29,30,33,37,41],"span",{"class":31,"line":32},"line",1,[29,34,36],{"class":35},"s7eDp","npm",[29,38,40],{"class":39},"sYBdl"," install",[29,42,43],{"class":39}," @kleora-io\u002Fbrowser\n",[45,46,48],"h2",{"id":47},"_1-one-client-created-in-the-browser","1. One client, created in the browser",[11,50,51,52,55],{},"Next.js evaluates a client component on the server too, so the instance is\nbuilt lazily and only where a browser exists. Reaching for ",[15,53,54],{},"location"," at module\nscope is the usual way this breaks during a build.",[20,57,61],{"className":58,"code":59,"language":60,"meta":25,"style":25},"language-ts shiki shiki-themes github-light","\u002F\u002F lib\u002Fkleora.ts\nimport { createKleora, type KleoraClient } from '@kleora-io\u002Fbrowser'\n\nlet client: KleoraClient | null = null\n\nexport function getKleora(): KleoraClient {\n  if (!client) {\n    client = createKleora({\n      issuer: process.env.NEXT_PUBLIC_KLEORA_ISSUER!,\n      clientId: process.env.NEXT_PUBLIC_KLEORA_CLIENT_ID!,\n      redirectUri: window.location.origin + '\u002Fcallback',\n      storage: 'session',\n    })\n  }\n  return client\n}\n","ts",[15,62,63,69,92,99,127,132,154,169,184,198,211,225,236,242,248,257],{"__ignoreMap":25},[29,64,65],{"class":31,"line":32},[29,66,68],{"class":67},"sAwPA","\u002F\u002F lib\u002Fkleora.ts\n",[29,70,72,76,80,83,86,89],{"class":31,"line":71},2,[29,73,75],{"class":74},"sD7c4","import",[29,77,79],{"class":78},"sgsFI"," { createKleora, ",[29,81,82],{"class":74},"type",[29,84,85],{"class":78}," KleoraClient } ",[29,87,88],{"class":74},"from",[29,90,91],{"class":39}," '@kleora-io\u002Fbrowser'\n",[29,93,95],{"class":31,"line":94},3,[29,96,98],{"emptyLinePlaceholder":97},true,"\n",[29,100,102,105,108,111,114,117,121,124],{"class":31,"line":101},4,[29,103,104],{"class":74},"let",[29,106,107],{"class":78}," client",[29,109,110],{"class":74},":",[29,112,113],{"class":35}," KleoraClient",[29,115,116],{"class":74}," |",[29,118,120],{"class":119},"sYu0t"," null",[29,122,123],{"class":74}," =",[29,125,126],{"class":119}," null\n",[29,128,130],{"class":31,"line":129},5,[29,131,98],{"emptyLinePlaceholder":97},[29,133,135,138,141,144,147,149,151],{"class":31,"line":134},6,[29,136,137],{"class":74},"export",[29,139,140],{"class":74}," function",[29,142,143],{"class":35}," getKleora",[29,145,146],{"class":78},"()",[29,148,110],{"class":74},[29,150,113],{"class":35},[29,152,153],{"class":78}," {\n",[29,155,157,160,163,166],{"class":31,"line":156},7,[29,158,159],{"class":74},"  if",[29,161,162],{"class":78}," (",[29,164,165],{"class":74},"!",[29,167,168],{"class":78},"client) {\n",[29,170,172,175,178,181],{"class":31,"line":171},8,[29,173,174],{"class":78},"    client ",[29,176,177],{"class":74},"=",[29,179,180],{"class":35}," createKleora",[29,182,183],{"class":78},"({\n",[29,185,187,190,193,195],{"class":31,"line":186},9,[29,188,189],{"class":78},"      issuer: process.env.",[29,191,192],{"class":119},"NEXT_PUBLIC_KLEORA_ISSUER",[29,194,165],{"class":74},[29,196,197],{"class":78},",\n",[29,199,201,204,207,209],{"class":31,"line":200},10,[29,202,203],{"class":78},"      clientId: process.env.",[29,205,206],{"class":119},"NEXT_PUBLIC_KLEORA_CLIENT_ID",[29,208,165],{"class":74},[29,210,197],{"class":78},[29,212,214,217,220,223],{"class":31,"line":213},11,[29,215,216],{"class":78},"      redirectUri: window.location.origin ",[29,218,219],{"class":74},"+",[29,221,222],{"class":39}," '\u002Fcallback'",[29,224,197],{"class":78},[29,226,228,231,234],{"class":31,"line":227},12,[29,229,230],{"class":78},"      storage: ",[29,232,233],{"class":39},"'session'",[29,235,197],{"class":78},[29,237,239],{"class":31,"line":238},13,[29,240,241],{"class":78},"    })\n",[29,243,245],{"class":31,"line":244},14,[29,246,247],{"class":78},"  }\n",[29,249,251,254],{"class":31,"line":250},15,[29,252,253],{"class":74},"  return",[29,255,256],{"class":78}," client\n",[29,258,260],{"class":31,"line":259},16,[29,261,262],{"class":78},"}\n",[11,264,265,266,269],{},"Call ",[15,267,268],{},"getKleora()"," from an effect or an event handler — never while\nrendering.",[45,271,273],{"id":272},"_2-the-callback-route","2. The callback route",[11,275,276,279,280,283,284,287],{},[15,277,278],{},"app\u002Fcallback\u002Fpage.tsx",", matching the ",[15,281,282],{},"redirectUri"," above and the\n",[15,285,286],{},"http:\u002F\u002Flocalhost:3000\u002Fcallback"," you registered on the client:",[20,289,293],{"className":290,"code":291,"language":292,"meta":25,"style":25},"language-tsx shiki shiki-themes github-light","'use client'\n\nimport { useEffect } from 'react'\nimport { useRouter } from 'next\u002Fnavigation'\nimport { getKleora } from '@\u002Flib\u002Fkleora'\n\nexport default function Callback() {\n  const router = useRouter()\n\n  useEffect(() => {\n    getKleora()\n      .handleRedirectCallback()\n      .then(({ appState }) => {\n        const returnTo = (appState as { returnTo?: string })?.returnTo\n        router.replace(returnTo ?? '\u002F')\n      })\n      .catch(() => router.replace('\u002F'))\n  }, [router])\n\n  return \u003Cp>Signing you in…\u003C\u002Fp>\n}\n","tsx",[15,294,295,300,304,316,328,340,344,359,375,379,392,399,409,430,461,481,486,512,518,523,542],{"__ignoreMap":25},[29,296,297],{"class":31,"line":32},[29,298,299],{"class":39},"'use client'\n",[29,301,302],{"class":31,"line":71},[29,303,98],{"emptyLinePlaceholder":97},[29,305,306,308,311,313],{"class":31,"line":94},[29,307,75],{"class":74},[29,309,310],{"class":78}," { useEffect } ",[29,312,88],{"class":74},[29,314,315],{"class":39}," 'react'\n",[29,317,318,320,323,325],{"class":31,"line":101},[29,319,75],{"class":74},[29,321,322],{"class":78}," { useRouter } ",[29,324,88],{"class":74},[29,326,327],{"class":39}," 'next\u002Fnavigation'\n",[29,329,330,332,335,337],{"class":31,"line":129},[29,331,75],{"class":74},[29,333,334],{"class":78}," { getKleora } ",[29,336,88],{"class":74},[29,338,339],{"class":39}," '@\u002Flib\u002Fkleora'\n",[29,341,342],{"class":31,"line":134},[29,343,98],{"emptyLinePlaceholder":97},[29,345,346,348,351,353,356],{"class":31,"line":156},[29,347,137],{"class":74},[29,349,350],{"class":74}," default",[29,352,140],{"class":74},[29,354,355],{"class":35}," Callback",[29,357,358],{"class":78},"() {\n",[29,360,361,364,367,369,372],{"class":31,"line":171},[29,362,363],{"class":74},"  const",[29,365,366],{"class":119}," router",[29,368,123],{"class":74},[29,370,371],{"class":35}," useRouter",[29,373,374],{"class":78},"()\n",[29,376,377],{"class":31,"line":186},[29,378,98],{"emptyLinePlaceholder":97},[29,380,381,384,387,390],{"class":31,"line":200},[29,382,383],{"class":35},"  useEffect",[29,385,386],{"class":78},"(() ",[29,388,389],{"class":74},"=>",[29,391,153],{"class":78},[29,393,394,397],{"class":31,"line":213},[29,395,396],{"class":35},"    getKleora",[29,398,374],{"class":78},[29,400,401,404,407],{"class":31,"line":227},[29,402,403],{"class":78},"      .",[29,405,406],{"class":35},"handleRedirectCallback",[29,408,374],{"class":78},[29,410,411,413,416,419,423,426,428],{"class":31,"line":238},[29,412,403],{"class":78},[29,414,415],{"class":35},"then",[29,417,418],{"class":78},"(({ ",[29,420,422],{"class":421},"sqxcx","appState",[29,424,425],{"class":78}," }) ",[29,427,389],{"class":74},[29,429,153],{"class":78},[29,431,432,435,438,440,443,446,449,452,455,458],{"class":31,"line":244},[29,433,434],{"class":74},"        const",[29,436,437],{"class":119}," returnTo",[29,439,123],{"class":74},[29,441,442],{"class":78}," (appState ",[29,444,445],{"class":74},"as",[29,447,448],{"class":78}," { ",[29,450,451],{"class":421},"returnTo",[29,453,454],{"class":74},"?:",[29,456,457],{"class":119}," string",[29,459,460],{"class":78}," })?.returnTo\n",[29,462,463,466,469,472,475,478],{"class":31,"line":250},[29,464,465],{"class":78},"        router.",[29,467,468],{"class":35},"replace",[29,470,471],{"class":78},"(returnTo ",[29,473,474],{"class":74},"??",[29,476,477],{"class":39}," '\u002F'",[29,479,480],{"class":78},")\n",[29,482,483],{"class":31,"line":259},[29,484,485],{"class":78},"      })\n",[29,487,489,491,494,496,498,501,503,506,509],{"class":31,"line":488},17,[29,490,403],{"class":78},[29,492,493],{"class":35},"catch",[29,495,386],{"class":78},[29,497,389],{"class":74},[29,499,500],{"class":78}," router.",[29,502,468],{"class":35},[29,504,505],{"class":78},"(",[29,507,508],{"class":39},"'\u002F'",[29,510,511],{"class":78},"))\n",[29,513,515],{"class":31,"line":514},18,[29,516,517],{"class":78},"  }, [router])\n",[29,519,521],{"class":31,"line":520},19,[29,522,98],{"emptyLinePlaceholder":97},[29,524,526,528,531,534,537,539],{"class":31,"line":525},20,[29,527,253],{"class":74},[29,529,530],{"class":78}," \u003C",[29,532,11],{"class":533},"shJU0",[29,535,536],{"class":78},">Signing you in…\u003C\u002F",[29,538,11],{"class":533},[29,540,541],{"class":78},">\n",[29,543,545],{"class":31,"line":544},21,[29,546,262],{"class":78},[45,548,550],{"id":549},"_3-a-hook-the-rest-of-the-application-can-use","3. A hook the rest of the application can use",[20,552,554],{"className":290,"code":553,"language":292,"meta":25,"style":25},"'use client'\n\nimport { useEffect, useState } from 'react'\nimport { getKleora } from '@\u002Flib\u002Fkleora'\nimport type { User } from '@kleora-io\u002Fbrowser'\n\nexport function useUser() {\n  const [user, setUser] = useState\u003CUser | null>(null)\n  const [loading, setLoading] = useState(true)\n\n  useEffect(() => {\n    const kleora = getKleora()\n    const unsubscribe = kleora.onAuthChange((state) => setUser(state.user))\n\n    kleora\n      .checkSession()\n      .then(() => setUser(kleora.getUser()))\n      .finally(() => setLoading(false))\n\n    return unsubscribe\n  }, [])\n\n  return { user, loading }\n}\n",[15,555,556,560,564,575,585,599,603,614,656,683,687,697,711,743,747,752,761,782,803,807,815,820,825,833],{"__ignoreMap":25},[29,557,558],{"class":31,"line":32},[29,559,299],{"class":39},[29,561,562],{"class":31,"line":71},[29,563,98],{"emptyLinePlaceholder":97},[29,565,566,568,571,573],{"class":31,"line":94},[29,567,75],{"class":74},[29,569,570],{"class":78}," { useEffect, useState } ",[29,572,88],{"class":74},[29,574,315],{"class":39},[29,576,577,579,581,583],{"class":31,"line":101},[29,578,75],{"class":74},[29,580,334],{"class":78},[29,582,88],{"class":74},[29,584,339],{"class":39},[29,586,587,589,592,595,597],{"class":31,"line":129},[29,588,75],{"class":74},[29,590,591],{"class":74}," type",[29,593,594],{"class":78}," { User } ",[29,596,88],{"class":74},[29,598,91],{"class":39},[29,600,601],{"class":31,"line":134},[29,602,98],{"emptyLinePlaceholder":97},[29,604,605,607,609,612],{"class":31,"line":156},[29,606,137],{"class":74},[29,608,140],{"class":74},[29,610,611],{"class":35}," useUser",[29,613,358],{"class":78},[29,615,616,618,621,624,627,630,633,635,638,641,644,646,648,651,654],{"class":31,"line":171},[29,617,363],{"class":74},[29,619,620],{"class":78}," [",[29,622,623],{"class":119},"user",[29,625,626],{"class":78},", ",[29,628,629],{"class":119},"setUser",[29,631,632],{"class":78},"] ",[29,634,177],{"class":74},[29,636,637],{"class":35}," useState",[29,639,640],{"class":78},"\u003C",[29,642,643],{"class":35},"User",[29,645,116],{"class":74},[29,647,120],{"class":119},[29,649,650],{"class":78},">(",[29,652,653],{"class":119},"null",[29,655,480],{"class":78},[29,657,658,660,662,665,667,670,672,674,676,678,681],{"class":31,"line":186},[29,659,363],{"class":74},[29,661,620],{"class":78},[29,663,664],{"class":119},"loading",[29,666,626],{"class":78},[29,668,669],{"class":119},"setLoading",[29,671,632],{"class":78},[29,673,177],{"class":74},[29,675,637],{"class":35},[29,677,505],{"class":78},[29,679,680],{"class":119},"true",[29,682,480],{"class":78},[29,684,685],{"class":31,"line":200},[29,686,98],{"emptyLinePlaceholder":97},[29,688,689,691,693,695],{"class":31,"line":213},[29,690,383],{"class":35},[29,692,386],{"class":78},[29,694,389],{"class":74},[29,696,153],{"class":78},[29,698,699,702,705,707,709],{"class":31,"line":227},[29,700,701],{"class":74},"    const",[29,703,704],{"class":119}," kleora",[29,706,123],{"class":74},[29,708,143],{"class":35},[29,710,374],{"class":78},[29,712,713,715,718,720,723,726,729,732,735,737,740],{"class":31,"line":238},[29,714,701],{"class":74},[29,716,717],{"class":119}," unsubscribe",[29,719,123],{"class":74},[29,721,722],{"class":78}," kleora.",[29,724,725],{"class":35},"onAuthChange",[29,727,728],{"class":78},"((",[29,730,731],{"class":421},"state",[29,733,734],{"class":78},") ",[29,736,389],{"class":74},[29,738,739],{"class":35}," setUser",[29,741,742],{"class":78},"(state.user))\n",[29,744,745],{"class":31,"line":244},[29,746,98],{"emptyLinePlaceholder":97},[29,748,749],{"class":31,"line":250},[29,750,751],{"class":78},"    kleora\n",[29,753,754,756,759],{"class":31,"line":259},[29,755,403],{"class":78},[29,757,758],{"class":35},"checkSession",[29,760,374],{"class":78},[29,762,763,765,767,769,771,773,776,779],{"class":31,"line":488},[29,764,403],{"class":78},[29,766,415],{"class":35},[29,768,386],{"class":78},[29,770,389],{"class":74},[29,772,739],{"class":35},[29,774,775],{"class":78},"(kleora.",[29,777,778],{"class":35},"getUser",[29,780,781],{"class":78},"()))\n",[29,783,784,786,789,791,793,796,798,801],{"class":31,"line":514},[29,785,403],{"class":78},[29,787,788],{"class":35},"finally",[29,790,386],{"class":78},[29,792,389],{"class":74},[29,794,795],{"class":35}," setLoading",[29,797,505],{"class":78},[29,799,800],{"class":119},"false",[29,802,511],{"class":78},[29,804,805],{"class":31,"line":520},[29,806,98],{"emptyLinePlaceholder":97},[29,808,809,812],{"class":31,"line":525},[29,810,811],{"class":74},"    return",[29,813,814],{"class":78}," unsubscribe\n",[29,816,817],{"class":31,"line":544},[29,818,819],{"class":78},"  }, [])\n",[29,821,823],{"class":31,"line":822},22,[29,824,98],{"emptyLinePlaceholder":97},[29,826,828,830],{"class":31,"line":827},23,[29,829,253],{"class":74},[29,831,832],{"class":78}," { user, loading }\n",[29,834,836],{"class":31,"line":835},24,[29,837,262],{"class":78},[11,839,840],{},"Sending somebody to sign in is then one call, from an event handler or from an\neffect on a page that requires it:",[20,842,844],{"className":58,"code":843,"language":60,"meta":25,"style":25},"await getKleora().loginWithRedirect({ returnTo: window.location.pathname })\n",[15,845,846],{"__ignoreMap":25},[29,847,848,851,853,856,859],{"class":31,"line":32},[29,849,850],{"class":74},"await",[29,852,143],{"class":35},[29,854,855],{"class":78},"().",[29,857,858],{"class":35},"loginWithRedirect",[29,860,861],{"class":78},"({ returnTo: window.location.pathname })\n",[863,864,867],"callout",{"title":865,"type":866},"A Server Component cannot see the session","warning",[11,868,869,870,873,874,877,878,881],{},"Tokens live in the browser — in memory, or in ",[15,871,872],{},"sessionStorage"," under\n",[15,875,876],{},"storage: 'session'"," — and are never sent to the Next.js server on their own.\nSo ",[15,879,880],{},"cookies()"," in a Server Component will not find a Kleora session, and a\npage that must render differently for a signed-in person has to make that\ndecision on the client, or fetch the data through a route handler with the\naccess token attached.",[45,883,885],{"id":884},"_4-verify-the-token-in-a-route-handler","4. Verify the token in a route handler",[11,887,888,889,892],{},"Your route handlers are an API like any other, and they verify the access token\noffline against the issuer's published key set. ",[15,890,891],{},"@kleora-io\u002Fnode"," declares\nNode 20+, so run the route on the Node runtime rather than the edge one.",[20,894,896],{"className":58,"code":895,"language":60,"meta":25,"style":25},"\u002F\u002F app\u002Fapi\u002Finvoices\u002Froute.ts\nimport { verifyAccessToken, TokenError } from '@kleora-io\u002Fnode'\n\nexport const runtime = 'nodejs'\n\nexport async function GET(request: Request) {\n  const header = request.headers.get('authorization') ?? ''\n  if (!header.startsWith('Bearer ')) {\n    return Response.json({ detail: 'Unauthorized' }, { status: 401 })\n  }\n\n  try {\n    const claims = await verifyAccessToken(header.slice(7), {\n      issuer: process.env.KLEORA_ISSUER!,\n      audience: process.env.KLEORA_AUDIENCE!,\n    })\n    if (!claims.permissions.includes('invoices:read')) {\n      return Response.json({ detail: 'Forbidden' }, { status: 403 })\n    }\n    return Response.json({ tenant: claims.tenant })\n  } catch (error) {\n    if (error instanceof TokenError && error.code === 'issuer_unavailable') {\n      return Response.json({ detail: 'Issuer unavailable' }, { status: 503 })\n    }\n    return Response.json({ detail: 'Unauthorized' }, { status: 401 })\n  }\n}\n",[15,897,898,903,915,919,934,938,963,990,1012,1037,1041,1045,1052,1081,1092,1104,1108,1130,1151,1156,1167,1177,1204,1224,1228,1247,1252],{"__ignoreMap":25},[29,899,900],{"class":31,"line":32},[29,901,902],{"class":67},"\u002F\u002F app\u002Fapi\u002Finvoices\u002Froute.ts\n",[29,904,905,907,910,912],{"class":31,"line":71},[29,906,75],{"class":74},[29,908,909],{"class":78}," { verifyAccessToken, TokenError } ",[29,911,88],{"class":74},[29,913,914],{"class":39}," '@kleora-io\u002Fnode'\n",[29,916,917],{"class":31,"line":94},[29,918,98],{"emptyLinePlaceholder":97},[29,920,921,923,926,929,931],{"class":31,"line":101},[29,922,137],{"class":74},[29,924,925],{"class":74}," const",[29,927,928],{"class":119}," runtime",[29,930,123],{"class":74},[29,932,933],{"class":39}," 'nodejs'\n",[29,935,936],{"class":31,"line":129},[29,937,98],{"emptyLinePlaceholder":97},[29,939,940,942,945,947,950,952,955,957,960],{"class":31,"line":134},[29,941,137],{"class":74},[29,943,944],{"class":74}," async",[29,946,140],{"class":74},[29,948,949],{"class":35}," GET",[29,951,505],{"class":78},[29,953,954],{"class":421},"request",[29,956,110],{"class":74},[29,958,959],{"class":35}," Request",[29,961,962],{"class":78},") {\n",[29,964,965,967,970,972,975,978,980,983,985,987],{"class":31,"line":156},[29,966,363],{"class":74},[29,968,969],{"class":119}," header",[29,971,123],{"class":74},[29,973,974],{"class":78}," request.headers.",[29,976,977],{"class":35},"get",[29,979,505],{"class":78},[29,981,982],{"class":39},"'authorization'",[29,984,734],{"class":78},[29,986,474],{"class":74},[29,988,989],{"class":39}," ''\n",[29,991,992,994,996,998,1001,1004,1006,1009],{"class":31,"line":171},[29,993,159],{"class":74},[29,995,162],{"class":78},[29,997,165],{"class":74},[29,999,1000],{"class":78},"header.",[29,1002,1003],{"class":35},"startsWith",[29,1005,505],{"class":78},[29,1007,1008],{"class":39},"'Bearer '",[29,1010,1011],{"class":78},")) {\n",[29,1013,1014,1016,1019,1022,1025,1028,1031,1034],{"class":31,"line":186},[29,1015,811],{"class":74},[29,1017,1018],{"class":78}," Response.",[29,1020,1021],{"class":35},"json",[29,1023,1024],{"class":78},"({ detail: ",[29,1026,1027],{"class":39},"'Unauthorized'",[29,1029,1030],{"class":78}," }, { status: ",[29,1032,1033],{"class":119},"401",[29,1035,1036],{"class":78}," })\n",[29,1038,1039],{"class":31,"line":200},[29,1040,247],{"class":78},[29,1042,1043],{"class":31,"line":213},[29,1044,98],{"emptyLinePlaceholder":97},[29,1046,1047,1050],{"class":31,"line":227},[29,1048,1049],{"class":74},"  try",[29,1051,153],{"class":78},[29,1053,1054,1056,1059,1061,1064,1067,1070,1073,1075,1078],{"class":31,"line":238},[29,1055,701],{"class":74},[29,1057,1058],{"class":119}," claims",[29,1060,123],{"class":74},[29,1062,1063],{"class":74}," await",[29,1065,1066],{"class":35}," verifyAccessToken",[29,1068,1069],{"class":78},"(header.",[29,1071,1072],{"class":35},"slice",[29,1074,505],{"class":78},[29,1076,1077],{"class":119},"7",[29,1079,1080],{"class":78},"), {\n",[29,1082,1083,1085,1088,1090],{"class":31,"line":244},[29,1084,189],{"class":78},[29,1086,1087],{"class":119},"KLEORA_ISSUER",[29,1089,165],{"class":74},[29,1091,197],{"class":78},[29,1093,1094,1097,1100,1102],{"class":31,"line":250},[29,1095,1096],{"class":78},"      audience: process.env.",[29,1098,1099],{"class":119},"KLEORA_AUDIENCE",[29,1101,165],{"class":74},[29,1103,197],{"class":78},[29,1105,1106],{"class":31,"line":259},[29,1107,241],{"class":78},[29,1109,1110,1113,1115,1117,1120,1123,1125,1128],{"class":31,"line":488},[29,1111,1112],{"class":74},"    if",[29,1114,162],{"class":78},[29,1116,165],{"class":74},[29,1118,1119],{"class":78},"claims.permissions.",[29,1121,1122],{"class":35},"includes",[29,1124,505],{"class":78},[29,1126,1127],{"class":39},"'invoices:read'",[29,1129,1011],{"class":78},[29,1131,1132,1135,1137,1139,1141,1144,1146,1149],{"class":31,"line":514},[29,1133,1134],{"class":74},"      return",[29,1136,1018],{"class":78},[29,1138,1021],{"class":35},[29,1140,1024],{"class":78},[29,1142,1143],{"class":39},"'Forbidden'",[29,1145,1030],{"class":78},[29,1147,1148],{"class":119},"403",[29,1150,1036],{"class":78},[29,1152,1153],{"class":31,"line":520},[29,1154,1155],{"class":78},"    }\n",[29,1157,1158,1160,1162,1164],{"class":31,"line":525},[29,1159,811],{"class":74},[29,1161,1018],{"class":78},[29,1163,1021],{"class":35},[29,1165,1166],{"class":78},"({ tenant: claims.tenant })\n",[29,1168,1169,1172,1174],{"class":31,"line":544},[29,1170,1171],{"class":78},"  } ",[29,1173,493],{"class":74},[29,1175,1176],{"class":78}," (error) {\n",[29,1178,1179,1181,1184,1187,1190,1193,1196,1199,1202],{"class":31,"line":822},[29,1180,1112],{"class":74},[29,1182,1183],{"class":78}," (error ",[29,1185,1186],{"class":74},"instanceof",[29,1188,1189],{"class":35}," TokenError",[29,1191,1192],{"class":74}," &&",[29,1194,1195],{"class":78}," error.code ",[29,1197,1198],{"class":74},"===",[29,1200,1201],{"class":39}," 'issuer_unavailable'",[29,1203,962],{"class":78},[29,1205,1206,1208,1210,1212,1214,1217,1219,1222],{"class":31,"line":827},[29,1207,1134],{"class":74},[29,1209,1018],{"class":78},[29,1211,1021],{"class":35},[29,1213,1024],{"class":78},[29,1215,1216],{"class":39},"'Issuer unavailable'",[29,1218,1030],{"class":78},[29,1220,1221],{"class":119},"503",[29,1223,1036],{"class":78},[29,1225,1226],{"class":31,"line":835},[29,1227,1155],{"class":78},[29,1229,1231,1233,1235,1237,1239,1241,1243,1245],{"class":31,"line":1230},25,[29,1232,811],{"class":74},[29,1234,1018],{"class":78},[29,1236,1021],{"class":35},[29,1238,1024],{"class":78},[29,1240,1027],{"class":39},[29,1242,1030],{"class":78},[29,1244,1033],{"class":119},[29,1246,1036],{"class":78},[29,1248,1250],{"class":31,"line":1249},26,[29,1251,247],{"class":78},[29,1253,1255],{"class":31,"line":1254},27,[29,1256,262],{"class":78},[11,1258,1259,1262,1263,1266,1267,1270,1271,1274,1275,1278,1279,1283],{},[15,1260,1261],{},"verifyAccessToken"," takes the bare token string, which is what a ",[15,1264,1265],{},"Request","'s\n",[15,1268,1269],{},"Headers"," object gives you. ",[15,1272,1273],{},"authenticate()"," — the other entry point — expects\nan object with a ",[15,1276,1277],{},"headers.authorization"," ",[1280,1281,1282],"strong",{},"property",", which is Express's shape\nrather than the Fetch API's.",[11,1285,1286,1289,1290,1293,1294,1297,1298,1301,1302,1304,1305,1307],{},[15,1287,1288],{},"audience"," is your environment's ",[15,1291,1292],{},"api_audience"," if you have set one, and the\nclient id otherwise. The key set is fetched once and cached for as long as its\n",[15,1295,1296],{},"Cache-Control"," says; an unreachable issuer is ",[15,1299,1300],{},"issuer_unavailable",", which is a\n",[15,1303,1221],{}," from you rather than a ",[15,1306,1033],{}," that tells every caller its token is bad.",[11,1309,1310],{},"Sending the token is the client's half:",[20,1312,1314],{"className":58,"code":1313,"language":60,"meta":25,"style":25},"const token = await getKleora().getAccessToken()\nconst response = await fetch('\u002Fapi\u002Finvoices', {\n  headers: { Authorization: `Bearer ${token}` },\n})\n",[15,1315,1316,1337,1359,1376],{"__ignoreMap":25},[29,1317,1318,1321,1324,1326,1328,1330,1332,1335],{"class":31,"line":32},[29,1319,1320],{"class":74},"const",[29,1322,1323],{"class":119}," token",[29,1325,123],{"class":74},[29,1327,1063],{"class":74},[29,1329,143],{"class":35},[29,1331,855],{"class":78},[29,1333,1334],{"class":35},"getAccessToken",[29,1336,374],{"class":78},[29,1338,1339,1341,1344,1346,1348,1351,1353,1356],{"class":31,"line":71},[29,1340,1320],{"class":74},[29,1342,1343],{"class":119}," response",[29,1345,123],{"class":74},[29,1347,1063],{"class":74},[29,1349,1350],{"class":35}," fetch",[29,1352,505],{"class":78},[29,1354,1355],{"class":39},"'\u002Fapi\u002Finvoices'",[29,1357,1358],{"class":78},", {\n",[29,1360,1361,1364,1367,1370,1373],{"class":31,"line":94},[29,1362,1363],{"class":78},"  headers: { Authorization: ",[29,1365,1366],{"class":39},"`Bearer ${",[29,1368,1369],{"class":78},"token",[29,1371,1372],{"class":39},"}`",[29,1374,1375],{"class":78}," },\n",[29,1377,1378],{"class":31,"line":101},[29,1379,1380],{"class":78},"})\n",[1382,1383,1384],"style",{},"html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}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 pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}html pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}",{"title":25,"searchDepth":94,"depth":94,"links":1386},[1387,1388,1389,1390],{"id":47,"depth":71,"text":48},{"id":272,"depth":71,"text":273},{"id":549,"depth":71,"text":550},{"id":884,"depth":71,"text":885},"The browser SDK in an App Router application — a lazy client, a callback route, and token verification in a route handler.","md","guides\u002Fquickstarts\u002Fnextjs",{},"\u002Fen\u002Fdocs\u002Fguides\u002Fquickstarts\u002Fnextjs",false,{"title":5,"description":1391},"en\u002Fdocs\u002Fguides\u002Fquickstarts\u002Fnextjs","P1yDVsSdGBIJsTELRM0gyVtGkvTKh3A9BFjIKoVRoyk",[1401,1406,1411,1416,1422,1427,1432,1437,1443,1448,1449,1454,1460,1465],{"path":1402,"title":1403,"description":1404,"order":200,"key":1405},"\u002Fen\u002Fdocs\u002Fgetting-started","Getting started","From an empty project to a working sign-in, and the packages that get you there.","getting-started\u002Findex",{"path":1407,"title":1408,"description":1409,"order":525,"key":1410},"\u002Fen\u002Fdocs\u002Fgetting-started\u002Finstallation","Installing an SDK","The five packages we publish, what each one is for, and the configuration each one takes.","getting-started\u002Finstallation",{"path":1412,"title":1413,"description":1414,"order":200,"key":1415},"\u002Fen\u002Fdocs\u002Fgetting-started\u002Fquickstart","Quickstart","Create an App, wire up the browser SDK, and sign in for the first time — entirely in sandbox.","getting-started\u002Fquickstart",{"path":1417,"title":1418,"description":1419,"order":1420,"key":1421},"\u002Fen\u002Fdocs\u002Fguides\u002Farchitecture","How Kleora is put together","The shape of the hosted service — one address per environment, two API surfaces, and where state lives. And why self-hosting is not something we support today.",30,"guides\u002Farchitecture",{"path":1423,"title":1424,"description":1425,"order":525,"key":1426},"\u002Fen\u002Fdocs\u002Fguides\u002Fconcepts","Workspaces, roles and environments","The five words the product is built from, which of them your tokens carry, and how a change to a role reaches a running application.","guides\u002Fconcepts",{"path":1428,"title":1429,"description":1430,"order":525,"key":1431},"\u002Fen\u002Fdocs\u002Fguides","Guides","Wiring Kleora into your stack, the words the product uses, and how the hosted service is put together.","guides\u002Findex",{"path":1433,"title":1434,"description":1435,"order":200,"key":1436},"\u002Fen\u002Fdocs\u002Fguides\u002Fquickstarts\u002Fbrowser","Plain browser","One client instance, one callback route, and an access token for every request your application makes.","guides\u002Fquickstarts\u002Fbrowser",{"path":1438,"title":1439,"description":1440,"order":1441,"key":1442},"\u002Fen\u002Fdocs\u002Fguides\u002Fquickstarts\u002Fexpress","Express","Verifying the access token on your own API — middleware, permissions, and the three answers a refusal can have.",40,"guides\u002Fquickstarts\u002Fexpress",{"path":1444,"title":1445,"description":1446,"order":200,"key":1447},"\u002Fen\u002Fdocs\u002Fguides\u002Fquickstarts","Framework quickstarts","One page per stack — Next.js, Nuxt, Express and the plain browser — and the two redirect URIs all of them share.","guides\u002Fquickstarts\u002Findex",{"path":1395,"title":5,"description":1391,"order":525,"key":1393},{"path":1450,"title":1451,"description":1452,"order":1420,"key":1453},"\u002Fen\u002Fdocs\u002Fguides\u002Fquickstarts\u002Fnuxt","Nuxt","The Nuxt module — two lines of config, a callback page you do not write, and a route middleware that protects a page from its own meta.","guides\u002Fquickstarts\u002Fnuxt",{"path":1455,"title":1456,"description":1457,"order":1458,"key":1459},"\u002Fen\u002Fdocs","Kleora documentation","Add hosted sign-in to your application, verify the token on your API, and manage users, workspaces and roles from one console.",0,"index",{"path":1461,"title":1462,"description":1463,"order":200,"key":1464},"\u002Fen\u002Fdocs\u002Freference\u002Fcode-highlighting","Code highlighting","One fence per preloaded Shiki grammar — a fixture, not a reference page.","reference\u002Fcode-highlighting",{"path":1466,"title":1467,"description":1468,"order":1420,"key":1469},"\u002Fen\u002Fdocs\u002Freference","Reference","Reference material for the HTTP APIs and the SDK packages.","reference\u002Findex",1790698197634]