[{"data":1,"prerenderedAt":1473},["ShallowReactive",2],{"docs:doc:\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fnextjs":3,"docs:pages:docs_pl":1403},{"id":4,"title":5,"alt":6,"body":7,"description":1394,"extension":1395,"key":1396,"meta":1397,"navigation":97,"order":524,"path":1398,"placeholder":1399,"seo":1400,"stem":1401,"__hash__":1402},"docs_pl\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fnextjs.md","Next.js","\u002Fen\u002Fdocs\u002Fguides\u002Fquickstarts\u002Fnextjs",{"type":8,"value":9,"toc":1388},"minimark",[10,19,44,49,56,263,270,274,287,546,550,837,840,861,881,885,896,1260,1287,1311,1314,1384],[11,12,13,14,18],"p",{},"Paczki dla Next.js nie ma i ta strona pokazuje, dlaczego nie musi być:\n",[15,16,17],"code",{},"@kleora-io\u002Fbrowser"," nie zna żadnego frameworka, a całe logowanie dzieje się\nw przeglądarce. Poniżej App Router; w Pages Routerze zmienia się tylko to, gdzie\nleżą te dwa pliki.",[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-jeden-klient-tworzony-w-przeglądarce","1. Jeden klient, tworzony w przeglądarce",[11,50,51,52,55],{},"Next.js wykonuje komponent kliencki także na serwerze, więc instancja powstaje\nleniwie i tylko tam, gdzie istnieje przeglądarka. Sięgnięcie po ",[15,53,54],{},"location"," na\npoziomie modułu to typowy sposób, w jaki się to psuje podczas builda.",[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],{},"Wołaj ",[15,267,268],{},"getKleora()"," z efektu albo z obsługi zdarzenia — nigdy w trakcie\nrenderowania.",[45,271,273],{"id":272},"_2-trasa-powrotna","2. Trasa powrotna",[11,275,276,279,280,283,284,110],{},[15,277,278],{},"app\u002Fcallback\u002Fpage.tsx",", zgodna z ",[15,281,282],{},"redirectUri"," powyżej i z zarejestrowanym na\nkliencie ",[15,285,286],{},"http:\u002F\u002Flocalhost:3000\u002Fcallback",[20,288,292],{"className":289,"code":290,"language":291,"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>Logujemy Cię…\u003C\u002Fp>\n}\n","tsx",[15,293,294,299,303,315,327,339,343,358,374,378,391,398,408,429,460,480,485,511,517,522,541],{"__ignoreMap":25},[29,295,296],{"class":31,"line":32},[29,297,298],{"class":39},"'use client'\n",[29,300,301],{"class":31,"line":71},[29,302,98],{"emptyLinePlaceholder":97},[29,304,305,307,310,312],{"class":31,"line":94},[29,306,75],{"class":74},[29,308,309],{"class":78}," { useEffect } ",[29,311,88],{"class":74},[29,313,314],{"class":39}," 'react'\n",[29,316,317,319,322,324],{"class":31,"line":101},[29,318,75],{"class":74},[29,320,321],{"class":78}," { useRouter } ",[29,323,88],{"class":74},[29,325,326],{"class":39}," 'next\u002Fnavigation'\n",[29,328,329,331,334,336],{"class":31,"line":129},[29,330,75],{"class":74},[29,332,333],{"class":78}," { getKleora } ",[29,335,88],{"class":74},[29,337,338],{"class":39}," '@\u002Flib\u002Fkleora'\n",[29,340,341],{"class":31,"line":134},[29,342,98],{"emptyLinePlaceholder":97},[29,344,345,347,350,352,355],{"class":31,"line":156},[29,346,137],{"class":74},[29,348,349],{"class":74}," default",[29,351,140],{"class":74},[29,353,354],{"class":35}," Callback",[29,356,357],{"class":78},"() {\n",[29,359,360,363,366,368,371],{"class":31,"line":171},[29,361,362],{"class":74},"  const",[29,364,365],{"class":119}," router",[29,367,123],{"class":74},[29,369,370],{"class":35}," useRouter",[29,372,373],{"class":78},"()\n",[29,375,376],{"class":31,"line":186},[29,377,98],{"emptyLinePlaceholder":97},[29,379,380,383,386,389],{"class":31,"line":200},[29,381,382],{"class":35},"  useEffect",[29,384,385],{"class":78},"(() ",[29,387,388],{"class":74},"=>",[29,390,153],{"class":78},[29,392,393,396],{"class":31,"line":213},[29,394,395],{"class":35},"    getKleora",[29,397,373],{"class":78},[29,399,400,403,406],{"class":31,"line":227},[29,401,402],{"class":78},"      .",[29,404,405],{"class":35},"handleRedirectCallback",[29,407,373],{"class":78},[29,409,410,412,415,418,422,425,427],{"class":31,"line":238},[29,411,402],{"class":78},[29,413,414],{"class":35},"then",[29,416,417],{"class":78},"(({ ",[29,419,421],{"class":420},"sqxcx","appState",[29,423,424],{"class":78}," }) ",[29,426,388],{"class":74},[29,428,153],{"class":78},[29,430,431,434,437,439,442,445,448,451,454,457],{"class":31,"line":244},[29,432,433],{"class":74},"        const",[29,435,436],{"class":119}," returnTo",[29,438,123],{"class":74},[29,440,441],{"class":78}," (appState ",[29,443,444],{"class":74},"as",[29,446,447],{"class":78}," { ",[29,449,450],{"class":420},"returnTo",[29,452,453],{"class":74},"?:",[29,455,456],{"class":119}," string",[29,458,459],{"class":78}," })?.returnTo\n",[29,461,462,465,468,471,474,477],{"class":31,"line":250},[29,463,464],{"class":78},"        router.",[29,466,467],{"class":35},"replace",[29,469,470],{"class":78},"(returnTo ",[29,472,473],{"class":74},"??",[29,475,476],{"class":39}," '\u002F'",[29,478,479],{"class":78},")\n",[29,481,482],{"class":31,"line":259},[29,483,484],{"class":78},"      })\n",[29,486,488,490,493,495,497,500,502,505,508],{"class":31,"line":487},17,[29,489,402],{"class":78},[29,491,492],{"class":35},"catch",[29,494,385],{"class":78},[29,496,388],{"class":74},[29,498,499],{"class":78}," router.",[29,501,467],{"class":35},[29,503,504],{"class":78},"(",[29,506,507],{"class":39},"'\u002F'",[29,509,510],{"class":78},"))\n",[29,512,514],{"class":31,"line":513},18,[29,515,516],{"class":78},"  }, [router])\n",[29,518,520],{"class":31,"line":519},19,[29,521,98],{"emptyLinePlaceholder":97},[29,523,525,527,530,533,536,538],{"class":31,"line":524},20,[29,526,253],{"class":74},[29,528,529],{"class":78}," \u003C",[29,531,11],{"class":532},"shJU0",[29,534,535],{"class":78},">Logujemy Cię…\u003C\u002F",[29,537,11],{"class":532},[29,539,540],{"class":78},">\n",[29,542,544],{"class":31,"line":543},21,[29,545,262],{"class":78},[45,547,549],{"id":548},"_3-hook-dla-reszty-aplikacji","3. Hook dla reszty aplikacji",[20,551,553],{"className":289,"code":552,"language":291,"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,554,555,559,563,574,584,598,602,613,655,682,686,696,710,742,746,751,760,781,802,806,814,819,824,832],{"__ignoreMap":25},[29,556,557],{"class":31,"line":32},[29,558,298],{"class":39},[29,560,561],{"class":31,"line":71},[29,562,98],{"emptyLinePlaceholder":97},[29,564,565,567,570,572],{"class":31,"line":94},[29,566,75],{"class":74},[29,568,569],{"class":78}," { useEffect, useState } ",[29,571,88],{"class":74},[29,573,314],{"class":39},[29,575,576,578,580,582],{"class":31,"line":101},[29,577,75],{"class":74},[29,579,333],{"class":78},[29,581,88],{"class":74},[29,583,338],{"class":39},[29,585,586,588,591,594,596],{"class":31,"line":129},[29,587,75],{"class":74},[29,589,590],{"class":74}," type",[29,592,593],{"class":78}," { User } ",[29,595,88],{"class":74},[29,597,91],{"class":39},[29,599,600],{"class":31,"line":134},[29,601,98],{"emptyLinePlaceholder":97},[29,603,604,606,608,611],{"class":31,"line":156},[29,605,137],{"class":74},[29,607,140],{"class":74},[29,609,610],{"class":35}," useUser",[29,612,357],{"class":78},[29,614,615,617,620,623,626,629,632,634,637,640,643,645,647,650,653],{"class":31,"line":171},[29,616,362],{"class":74},[29,618,619],{"class":78}," [",[29,621,622],{"class":119},"user",[29,624,625],{"class":78},", ",[29,627,628],{"class":119},"setUser",[29,630,631],{"class":78},"] ",[29,633,177],{"class":74},[29,635,636],{"class":35}," useState",[29,638,639],{"class":78},"\u003C",[29,641,642],{"class":35},"User",[29,644,116],{"class":74},[29,646,120],{"class":119},[29,648,649],{"class":78},">(",[29,651,652],{"class":119},"null",[29,654,479],{"class":78},[29,656,657,659,661,664,666,669,671,673,675,677,680],{"class":31,"line":186},[29,658,362],{"class":74},[29,660,619],{"class":78},[29,662,663],{"class":119},"loading",[29,665,625],{"class":78},[29,667,668],{"class":119},"setLoading",[29,670,631],{"class":78},[29,672,177],{"class":74},[29,674,636],{"class":35},[29,676,504],{"class":78},[29,678,679],{"class":119},"true",[29,681,479],{"class":78},[29,683,684],{"class":31,"line":200},[29,685,98],{"emptyLinePlaceholder":97},[29,687,688,690,692,694],{"class":31,"line":213},[29,689,382],{"class":35},[29,691,385],{"class":78},[29,693,388],{"class":74},[29,695,153],{"class":78},[29,697,698,701,704,706,708],{"class":31,"line":227},[29,699,700],{"class":74},"    const",[29,702,703],{"class":119}," kleora",[29,705,123],{"class":74},[29,707,143],{"class":35},[29,709,373],{"class":78},[29,711,712,714,717,719,722,725,728,731,734,736,739],{"class":31,"line":238},[29,713,700],{"class":74},[29,715,716],{"class":119}," unsubscribe",[29,718,123],{"class":74},[29,720,721],{"class":78}," kleora.",[29,723,724],{"class":35},"onAuthChange",[29,726,727],{"class":78},"((",[29,729,730],{"class":420},"state",[29,732,733],{"class":78},") ",[29,735,388],{"class":74},[29,737,738],{"class":35}," setUser",[29,740,741],{"class":78},"(state.user))\n",[29,743,744],{"class":31,"line":244},[29,745,98],{"emptyLinePlaceholder":97},[29,747,748],{"class":31,"line":250},[29,749,750],{"class":78},"    kleora\n",[29,752,753,755,758],{"class":31,"line":259},[29,754,402],{"class":78},[29,756,757],{"class":35},"checkSession",[29,759,373],{"class":78},[29,761,762,764,766,768,770,772,775,778],{"class":31,"line":487},[29,763,402],{"class":78},[29,765,414],{"class":35},[29,767,385],{"class":78},[29,769,388],{"class":74},[29,771,738],{"class":35},[29,773,774],{"class":78},"(kleora.",[29,776,777],{"class":35},"getUser",[29,779,780],{"class":78},"()))\n",[29,782,783,785,788,790,792,795,797,800],{"class":31,"line":513},[29,784,402],{"class":78},[29,786,787],{"class":35},"finally",[29,789,385],{"class":78},[29,791,388],{"class":74},[29,793,794],{"class":35}," setLoading",[29,796,504],{"class":78},[29,798,799],{"class":119},"false",[29,801,510],{"class":78},[29,803,804],{"class":31,"line":519},[29,805,98],{"emptyLinePlaceholder":97},[29,807,808,811],{"class":31,"line":524},[29,809,810],{"class":74},"    return",[29,812,813],{"class":78}," unsubscribe\n",[29,815,816],{"class":31,"line":543},[29,817,818],{"class":78},"  }, [])\n",[29,820,822],{"class":31,"line":821},22,[29,823,98],{"emptyLinePlaceholder":97},[29,825,827,829],{"class":31,"line":826},23,[29,828,253],{"class":74},[29,830,831],{"class":78}," { user, loading }\n",[29,833,835],{"class":31,"line":834},24,[29,836,262],{"class":78},[11,838,839],{},"Wysłanie kogoś do logowania to wtedy jedno wywołanie — z obsługi zdarzenia albo\nz efektu na stronie, która tego wymaga:",[20,841,843],{"className":58,"code":842,"language":60,"meta":25,"style":25},"await getKleora().loginWithRedirect({ returnTo: window.location.pathname })\n",[15,844,845],{"__ignoreMap":25},[29,846,847,850,852,855,858],{"class":31,"line":32},[29,848,849],{"class":74},"await",[29,851,143],{"class":35},[29,853,854],{"class":78},"().",[29,856,857],{"class":35},"loginWithRedirect",[29,859,860],{"class":78},"({ returnTo: window.location.pathname })\n",[862,863,866],"callout",{"title":864,"type":865},"Server Component nie widzi sesji","warning",[11,867,868,869,872,873,876,877,880],{},"Tokeny żyją w przeglądarce — w pamięci albo w ",[15,870,871],{},"sessionStorage"," przy\n",[15,874,875],{},"storage: 'session'"," — i same z siebie nigdy nie trafiają na serwer Next.js.\n",[15,878,879],{},"cookies()"," w Server Component nie znajdzie więc sesji z usługi Kleora, a strona, która\nma wyglądać inaczej dla osoby zalogowanej, musi podjąć tę decyzję po stronie\nklienta albo pobrać dane przez route handler z dołączonym tokenem dostępu.",[45,882,884],{"id":883},"_4-sprawdź-token-w-route-handlerze","4. Sprawdź token w route handlerze",[11,886,887,888,891,892,895],{},"Twoje route handlery to API jak każde inne i sprawdzają token dostępu offline,\nwzględem zestawu kluczy publikowanego przez issuer. ",[15,889,890],{},"@kleora-io\u002Fnode","\ndeklaruje Node 20+, więc uruchom trasę na runtime ",[15,893,894],{},"nodejs",", nie na edge.",[20,897,899],{"className":58,"code":898,"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,900,901,906,918,922,937,941,966,993,1015,1040,1044,1048,1055,1084,1095,1107,1111,1133,1154,1159,1170,1180,1207,1227,1231,1250,1255],{"__ignoreMap":25},[29,902,903],{"class":31,"line":32},[29,904,905],{"class":67},"\u002F\u002F app\u002Fapi\u002Finvoices\u002Froute.ts\n",[29,907,908,910,913,915],{"class":31,"line":71},[29,909,75],{"class":74},[29,911,912],{"class":78}," { verifyAccessToken, TokenError } ",[29,914,88],{"class":74},[29,916,917],{"class":39}," '@kleora-io\u002Fnode'\n",[29,919,920],{"class":31,"line":94},[29,921,98],{"emptyLinePlaceholder":97},[29,923,924,926,929,932,934],{"class":31,"line":101},[29,925,137],{"class":74},[29,927,928],{"class":74}," const",[29,930,931],{"class":119}," runtime",[29,933,123],{"class":74},[29,935,936],{"class":39}," 'nodejs'\n",[29,938,939],{"class":31,"line":129},[29,940,98],{"emptyLinePlaceholder":97},[29,942,943,945,948,950,953,955,958,960,963],{"class":31,"line":134},[29,944,137],{"class":74},[29,946,947],{"class":74}," async",[29,949,140],{"class":74},[29,951,952],{"class":35}," GET",[29,954,504],{"class":78},[29,956,957],{"class":420},"request",[29,959,110],{"class":74},[29,961,962],{"class":35}," Request",[29,964,965],{"class":78},") {\n",[29,967,968,970,973,975,978,981,983,986,988,990],{"class":31,"line":156},[29,969,362],{"class":74},[29,971,972],{"class":119}," header",[29,974,123],{"class":74},[29,976,977],{"class":78}," request.headers.",[29,979,980],{"class":35},"get",[29,982,504],{"class":78},[29,984,985],{"class":39},"'authorization'",[29,987,733],{"class":78},[29,989,473],{"class":74},[29,991,992],{"class":39}," ''\n",[29,994,995,997,999,1001,1004,1007,1009,1012],{"class":31,"line":171},[29,996,159],{"class":74},[29,998,162],{"class":78},[29,1000,165],{"class":74},[29,1002,1003],{"class":78},"header.",[29,1005,1006],{"class":35},"startsWith",[29,1008,504],{"class":78},[29,1010,1011],{"class":39},"'Bearer '",[29,1013,1014],{"class":78},")) {\n",[29,1016,1017,1019,1022,1025,1028,1031,1034,1037],{"class":31,"line":186},[29,1018,810],{"class":74},[29,1020,1021],{"class":78}," Response.",[29,1023,1024],{"class":35},"json",[29,1026,1027],{"class":78},"({ detail: ",[29,1029,1030],{"class":39},"'Unauthorized'",[29,1032,1033],{"class":78}," }, { status: ",[29,1035,1036],{"class":119},"401",[29,1038,1039],{"class":78}," })\n",[29,1041,1042],{"class":31,"line":200},[29,1043,247],{"class":78},[29,1045,1046],{"class":31,"line":213},[29,1047,98],{"emptyLinePlaceholder":97},[29,1049,1050,1053],{"class":31,"line":227},[29,1051,1052],{"class":74},"  try",[29,1054,153],{"class":78},[29,1056,1057,1059,1062,1064,1067,1070,1073,1076,1078,1081],{"class":31,"line":238},[29,1058,700],{"class":74},[29,1060,1061],{"class":119}," claims",[29,1063,123],{"class":74},[29,1065,1066],{"class":74}," await",[29,1068,1069],{"class":35}," verifyAccessToken",[29,1071,1072],{"class":78},"(header.",[29,1074,1075],{"class":35},"slice",[29,1077,504],{"class":78},[29,1079,1080],{"class":119},"7",[29,1082,1083],{"class":78},"), {\n",[29,1085,1086,1088,1091,1093],{"class":31,"line":244},[29,1087,189],{"class":78},[29,1089,1090],{"class":119},"KLEORA_ISSUER",[29,1092,165],{"class":74},[29,1094,197],{"class":78},[29,1096,1097,1100,1103,1105],{"class":31,"line":250},[29,1098,1099],{"class":78},"      audience: process.env.",[29,1101,1102],{"class":119},"KLEORA_AUDIENCE",[29,1104,165],{"class":74},[29,1106,197],{"class":78},[29,1108,1109],{"class":31,"line":259},[29,1110,241],{"class":78},[29,1112,1113,1116,1118,1120,1123,1126,1128,1131],{"class":31,"line":487},[29,1114,1115],{"class":74},"    if",[29,1117,162],{"class":78},[29,1119,165],{"class":74},[29,1121,1122],{"class":78},"claims.permissions.",[29,1124,1125],{"class":35},"includes",[29,1127,504],{"class":78},[29,1129,1130],{"class":39},"'invoices:read'",[29,1132,1014],{"class":78},[29,1134,1135,1138,1140,1142,1144,1147,1149,1152],{"class":31,"line":513},[29,1136,1137],{"class":74},"      return",[29,1139,1021],{"class":78},[29,1141,1024],{"class":35},[29,1143,1027],{"class":78},[29,1145,1146],{"class":39},"'Forbidden'",[29,1148,1033],{"class":78},[29,1150,1151],{"class":119},"403",[29,1153,1039],{"class":78},[29,1155,1156],{"class":31,"line":519},[29,1157,1158],{"class":78},"    }\n",[29,1160,1161,1163,1165,1167],{"class":31,"line":524},[29,1162,810],{"class":74},[29,1164,1021],{"class":78},[29,1166,1024],{"class":35},[29,1168,1169],{"class":78},"({ tenant: claims.tenant })\n",[29,1171,1172,1175,1177],{"class":31,"line":543},[29,1173,1174],{"class":78},"  } ",[29,1176,492],{"class":74},[29,1178,1179],{"class":78}," (error) {\n",[29,1181,1182,1184,1187,1190,1193,1196,1199,1202,1205],{"class":31,"line":821},[29,1183,1115],{"class":74},[29,1185,1186],{"class":78}," (error ",[29,1188,1189],{"class":74},"instanceof",[29,1191,1192],{"class":35}," TokenError",[29,1194,1195],{"class":74}," &&",[29,1197,1198],{"class":78}," error.code ",[29,1200,1201],{"class":74},"===",[29,1203,1204],{"class":39}," 'issuer_unavailable'",[29,1206,965],{"class":78},[29,1208,1209,1211,1213,1215,1217,1220,1222,1225],{"class":31,"line":826},[29,1210,1137],{"class":74},[29,1212,1021],{"class":78},[29,1214,1024],{"class":35},[29,1216,1027],{"class":78},[29,1218,1219],{"class":39},"'Issuer unavailable'",[29,1221,1033],{"class":78},[29,1223,1224],{"class":119},"503",[29,1226,1039],{"class":78},[29,1228,1229],{"class":31,"line":834},[29,1230,1158],{"class":78},[29,1232,1234,1236,1238,1240,1242,1244,1246,1248],{"class":31,"line":1233},25,[29,1235,810],{"class":74},[29,1237,1021],{"class":78},[29,1239,1024],{"class":35},[29,1241,1027],{"class":78},[29,1243,1030],{"class":39},[29,1245,1033],{"class":78},[29,1247,1036],{"class":119},[29,1249,1039],{"class":78},[29,1251,1253],{"class":31,"line":1252},26,[29,1254,247],{"class":78},[29,1256,1258],{"class":31,"line":1257},27,[29,1259,262],{"class":78},[11,1261,1262,1265,1266,1269,1270,1273,1274,1277,1278,1282,1283,1286],{},[15,1263,1264],{},"verifyAccessToken"," przyjmuje goły token, czyli dokładnie to, co daje obiekt\n",[15,1267,1268],{},"Headers"," z ",[15,1271,1272],{},"Request",". Drugie wejście, ",[15,1275,1276],{},"authenticate()",", oczekuje obiektu z\n",[1279,1280,1281],"strong",{},"właściwością"," ",[15,1284,1285],{},"headers.authorization"," — to kształt z Express, nie z Fetch API.",[11,1288,1289,1292,1293,1296,1297,1300,1301,1304,1305,1307,1308,1310],{},[15,1290,1291],{},"audience"," to ",[15,1294,1295],{},"api_audience"," Twojego środowiska, jeśli je ustawiłeś, a w\nprzeciwnym razie identyfikator klienta. Zestaw kluczy pobierany jest raz i\ntrzymany tak długo, jak mówi jego ",[15,1298,1299],{},"Cache-Control","; nieosiągalny issuer to\n",[15,1302,1303],{},"issuer_unavailable",", czyli ",[15,1306,1224],{}," od Ciebie zamiast ",[15,1309,1036],{},", który powiedziałby\nkażdemu wołającemu, że jego token jest zły.",[11,1312,1313],{},"Wysłanie tokenu to połowa po stronie klienta:",[20,1315,1317],{"className":58,"code":1316,"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,1318,1319,1340,1362,1379],{"__ignoreMap":25},[29,1320,1321,1324,1327,1329,1331,1333,1335,1338],{"class":31,"line":32},[29,1322,1323],{"class":74},"const",[29,1325,1326],{"class":119}," token",[29,1328,123],{"class":74},[29,1330,1066],{"class":74},[29,1332,143],{"class":35},[29,1334,854],{"class":78},[29,1336,1337],{"class":35},"getAccessToken",[29,1339,373],{"class":78},[29,1341,1342,1344,1347,1349,1351,1354,1356,1359],{"class":31,"line":71},[29,1343,1323],{"class":74},[29,1345,1346],{"class":119}," response",[29,1348,123],{"class":74},[29,1350,1066],{"class":74},[29,1352,1353],{"class":35}," fetch",[29,1355,504],{"class":78},[29,1357,1358],{"class":39},"'\u002Fapi\u002Finvoices'",[29,1360,1361],{"class":78},", {\n",[29,1363,1364,1367,1370,1373,1376],{"class":31,"line":94},[29,1365,1366],{"class":78},"  headers: { Authorization: ",[29,1368,1369],{"class":39},"`Bearer ${",[29,1371,1372],{"class":78},"token",[29,1374,1375],{"class":39},"}`",[29,1377,1378],{"class":78}," },\n",[29,1380,1381],{"class":31,"line":101},[29,1382,1383],{"class":78},"})\n",[1385,1386,1387],"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":1389},[1390,1391,1392,1393],{"id":47,"depth":71,"text":48},{"id":272,"depth":71,"text":273},{"id":548,"depth":71,"text":549},{"id":883,"depth":71,"text":884},"SDK przeglądarkowe w aplikacji na App Router — leniwy klient, trasa powrotna i sprawdzanie tokenu w route handlerze.","md","guides\u002Fquickstarts\u002Fnextjs",{},"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fnextjs",false,{"title":5,"description":1394},"pl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fnextjs","4aIfDXPl-8NZ1p096poWDc-Ci-uYdldoXId_iXYz6MU",[1404,1410,1415,1420,1425,1431,1437,1442,1443,1448,1453,1458,1463,1468],{"path":1405,"title":1406,"description":1407,"order":1408,"key":1409},"\u002Fpl\u002Fdocs","Dokumentacja Kleora","Dodaj hostowane logowanie do swojej aplikacji, sprawdź token w swoim API i zarządzaj użytkownikami, przestrzeniami roboczymi i rolami z jednej konsoli.",0,"index",{"path":1411,"title":1412,"description":1413,"order":200,"key":1414},"\u002Fpl\u002Fdocs\u002Fpierwsze-kroki","Pierwsze kroki","Od pustego projektu do działającego logowania — i paczki, które do tego prowadzą.","getting-started\u002Findex",{"path":1416,"title":1417,"description":1418,"order":524,"key":1419},"\u002Fpl\u002Fdocs\u002Fpierwsze-kroki\u002Finstalacja","Instalacja SDK","Pięć paczek, które publikujemy, do czego służy każda z nich i jakiej konfiguracji wymaga.","getting-started\u002Finstallation",{"path":1421,"title":1422,"description":1423,"order":200,"key":1424},"\u002Fpl\u002Fdocs\u002Fpierwsze-kroki\u002Fszybki-start","Szybki start","Załóż aplikację, podłącz SDK przeglądarkowe i zaloguj się po raz pierwszy — w całości w sandboksie.","getting-started\u002Fquickstart",{"path":1426,"title":1427,"description":1428,"order":1429,"key":1430},"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Farchitektura","Jak zbudowana jest Kleora","Kształt usługi hostowanej — jeden adres na środowisko, dwie powierzchnie API i miejsce, w którym leży stan. Oraz dlaczego hostowania u siebie dzisiaj nie wspieramy.",30,"guides\u002Farchitecture",{"path":1432,"title":1433,"description":1434,"order":1435,"key":1436},"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fexpress","Express","Sprawdzanie tokenu dostępu we własnym API — middleware, uprawnienia i trzy odpowiedzi, jakie może mieć odmowa.",40,"guides\u002Fquickstarts\u002Fexpress",{"path":1438,"title":1439,"description":1440,"order":200,"key":1441},"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki","Szybki start dla frameworków","Po jednej stronie na stos — Next.js, Nuxt, Express i zwykła przeglądarka — i dwa adresy powrotne, które łączą je wszystkie.","guides\u002Fquickstarts\u002Findex",{"path":1398,"title":5,"description":1394,"order":524,"key":1396},{"path":1444,"title":1445,"description":1446,"order":1429,"key":1447},"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fnuxt","Nuxt","Moduł dla Nuxt — dwie linijki konfiguracji, strona powrotu, której nie piszesz, i middleware chroniący stronę z jej własnych metadanych.","guides\u002Fquickstarts\u002Fnuxt",{"path":1449,"title":1450,"description":1451,"order":200,"key":1452},"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fprzegladarka","Zwykła przeglądarka","Jedna instancja klienta, jedna trasa powrotna i token dostępu do każdego żądania, które wysyła Twoja aplikacja.","guides\u002Fquickstarts\u002Fbrowser",{"path":1454,"title":1455,"description":1456,"order":524,"key":1457},"\u002Fpl\u002Fdocs\u002Fprzewodniki","Przewodniki","Jak podłączyć usługę Kleora do swojego stosu, jakich słów używa produkt i jak zbudowana jest usługa hostowana.","guides\u002Findex",{"path":1459,"title":1460,"description":1461,"order":524,"key":1462},"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fpojecia","Przestrzenie robocze, role i środowiska","Pięć słów, z których zbudowany jest produkt, to, które z nich niosą Twoje tokeny, i jak zmiana roli dociera do działającej aplikacji.","guides\u002Fconcepts",{"path":1464,"title":1465,"description":1466,"order":1429,"key":1467},"\u002Fpl\u002Fdocs\u002Freferencje","Referencje","Materiały referencyjne dla API HTTP i paczek SDK.","reference\u002Findex",{"path":1469,"title":1470,"description":1471,"order":200,"key":1472},"\u002Fpl\u002Fdocs\u002Freferencje\u002Fpodswietlanie","Podświetlanie kodu","Po jednym bloku na każdą wstępnie wczytaną gramatykę Shiki — próbka, nie strona referencyjna.","reference\u002Fcode-highlighting",1790698198103]