[{"data":1,"prerenderedAt":703},["ShallowReactive",2],{"docs:doc:\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fexpress":3,"docs:pages:docs_pl":635},{"id":4,"title":5,"alt":6,"body":7,"description":625,"extension":626,"key":627,"meta":628,"navigation":100,"order":629,"path":630,"placeholder":631,"seo":632,"stem":633,"__hash__":634},"docs_pl\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fexpress.md","Express","\u002Fen\u002Fdocs\u002Fguides\u002Fquickstarts\u002Fexpress",{"type":8,"value":9,"toc":617},"minimark",[10,19,45,48,53,254,268,396,403,426,430,495,513,520,534,538,572,586,590,600,613],[11,12,13,14,18],"p",{},"Druga połowa każdej strony w tej sekcji: przeglądarka ma token, a Twoje API musi\nzdecydować, co on znaczy. Weryfikacja jest ",[15,16,17],"strong",{},"offline"," — token to podpisany JWT,\nTwoje API sprawdza go względem zestawu kluczy publikowanego przez Twój issuer i\nnie pyta nas o nic przy każdym żądaniu.",[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\u002Fnode\n","bash","",[27,28,29],"code",{"__ignoreMap":25},[30,31,34,38,42],"span",{"class":32,"line":33},"line",1,[30,35,37],{"class":36},"s7eDp","npm",[30,39,41],{"class":40},"sYBdl"," install",[30,43,44],{"class":40}," @kleora-io\u002Fnode\n",[11,46,47],{},"Jedna zależność w czasie działania, biblioteka JOSE. Express i Fastify są\nopcjonalnymi peerami, więc paczka instaluje się bez obu.",[49,50,52],"h2",{"id":51},"_1-najpierw-uwierzytelnienie-potem-autoryzacja","1. Najpierw uwierzytelnienie, potem autoryzacja",[20,54,58],{"className":55,"code":56,"language":57,"meta":25,"style":25},"language-ts shiki shiki-themes github-light","import express from 'express'\nimport { express as kleora, requirePermission } from '@kleora-io\u002Fnode'\n\nconst app = express()\n\napp.use(\n  kleora({\n    issuer: process.env.KLEORA_ISSUER!,\n    audience: 'https:\u002F\u002Fapi.example.com',\n  }),\n)\n\napp.get('\u002Finvoices', requirePermission('invoices:read'), (req, res) => {\n  res.json({ tenant: req.kleora.tenant })\n})\n","ts",[27,59,60,76,95,102,121,126,138,147,162,173,179,185,190,236,248],{"__ignoreMap":25},[30,61,62,66,70,73],{"class":32,"line":33},[30,63,65],{"class":64},"sD7c4","import",[30,67,69],{"class":68},"sgsFI"," express ",[30,71,72],{"class":64},"from",[30,74,75],{"class":40}," 'express'\n",[30,77,79,81,84,87,90,92],{"class":32,"line":78},2,[30,80,65],{"class":64},[30,82,83],{"class":68}," { express ",[30,85,86],{"class":64},"as",[30,88,89],{"class":68}," kleora, requirePermission } ",[30,91,72],{"class":64},[30,93,94],{"class":40}," '@kleora-io\u002Fnode'\n",[30,96,98],{"class":32,"line":97},3,[30,99,101],{"emptyLinePlaceholder":100},true,"\n",[30,103,105,108,112,115,118],{"class":32,"line":104},4,[30,106,107],{"class":64},"const",[30,109,111],{"class":110},"sYu0t"," app",[30,113,114],{"class":64}," =",[30,116,117],{"class":36}," express",[30,119,120],{"class":68},"()\n",[30,122,124],{"class":32,"line":123},5,[30,125,101],{"emptyLinePlaceholder":100},[30,127,129,132,135],{"class":32,"line":128},6,[30,130,131],{"class":68},"app.",[30,133,134],{"class":36},"use",[30,136,137],{"class":68},"(\n",[30,139,141,144],{"class":32,"line":140},7,[30,142,143],{"class":36},"  kleora",[30,145,146],{"class":68},"({\n",[30,148,150,153,156,159],{"class":32,"line":149},8,[30,151,152],{"class":68},"    issuer: process.env.",[30,154,155],{"class":110},"KLEORA_ISSUER",[30,157,158],{"class":64},"!",[30,160,161],{"class":68},",\n",[30,163,165,168,171],{"class":32,"line":164},9,[30,166,167],{"class":68},"    audience: ",[30,169,170],{"class":40},"'https:\u002F\u002Fapi.example.com'",[30,172,161],{"class":68},[30,174,176],{"class":32,"line":175},10,[30,177,178],{"class":68},"  }),\n",[30,180,182],{"class":32,"line":181},11,[30,183,184],{"class":68},")\n",[30,186,188],{"class":32,"line":187},12,[30,189,101],{"emptyLinePlaceholder":100},[30,191,193,195,198,201,204,207,210,212,215,218,222,224,227,230,233],{"class":32,"line":192},13,[30,194,131],{"class":68},[30,196,197],{"class":36},"get",[30,199,200],{"class":68},"(",[30,202,203],{"class":40},"'\u002Finvoices'",[30,205,206],{"class":68},", ",[30,208,209],{"class":36},"requirePermission",[30,211,200],{"class":68},[30,213,214],{"class":40},"'invoices:read'",[30,216,217],{"class":68},"), (",[30,219,221],{"class":220},"sqxcx","req",[30,223,206],{"class":68},[30,225,226],{"class":220},"res",[30,228,229],{"class":68},") ",[30,231,232],{"class":64},"=>",[30,234,235],{"class":68}," {\n",[30,237,239,242,245],{"class":32,"line":238},14,[30,240,241],{"class":68},"  res.",[30,243,244],{"class":36},"json",[30,246,247],{"class":68},"({ tenant: req.kleora.tenant })\n",[30,249,251],{"class":32,"line":250},15,[30,252,253],{"class":68},"})\n",[11,255,256,257,260,261,263,264,267],{},"Middleware weryfikuje token i ustawia ",[27,258,259],{},"req.kleora"," na jego claimy.\n",[27,262,209],{}," czyta ",[27,265,266],{},"req.kleora.permissions"," — a te są już w tokenie,\nwięc sprawdzenie kosztuje tyle, co przejrzenie listy, a nie zapytanie do nas.",[269,270,273,286,356,359,382],"callout",{"title":271,"type":272},"Ten fragment wymaga od Ciebie jednej linijki TypeScriptu","note",[11,274,275,277,278,281,282,285],{},[27,276,259],{}," nie należy do typu ",[27,279,280],{},"Request"," z Express, a ta paczka celowo nie\ndeklaruje globalnego rozszerzenia — nie importuje żadnego frameworka, a paczka,\nktóra poszerzałaby ",[27,283,284],{},"express.Request"," każdemu, kto ją zainstaluje, sięgałaby poza\nsiebie. Albo dopisz rozszerzenie sam:",[20,287,289],{"className":55,"code":288,"language":57,"meta":25,"style":25},"declare global {\n  namespace Express {\n    interface Request {\n      kleora?: import('@kleora-io\u002Fnode').Claims\n    }\n  }\n}\n",[27,290,291,299,309,319,341,346,351],{"__ignoreMap":25},[30,292,293,296],{"class":32,"line":33},[30,294,295],{"class":64},"declare",[30,297,298],{"class":68}," global {\n",[30,300,301,304,307],{"class":32,"line":78},[30,302,303],{"class":64},"  namespace",[30,305,306],{"class":36}," Express",[30,308,235],{"class":68},[30,310,311,314,317],{"class":32,"line":97},[30,312,313],{"class":64},"    interface",[30,315,316],{"class":36}," Request",[30,318,235],{"class":68},[30,320,321,324,327,330,332,335,338],{"class":32,"line":104},[30,322,323],{"class":220},"      kleora",[30,325,326],{"class":64},"?:",[30,328,329],{"class":64}," import",[30,331,200],{"class":68},[30,333,334],{"class":40},"'@kleora-io\u002Fnode'",[30,336,337],{"class":68},").",[30,339,340],{"class":36},"Claims\n",[30,342,343],{"class":32,"line":123},[30,344,345],{"class":68},"    }\n",[30,347,348],{"class":32,"line":128},[30,349,350],{"class":68},"  }\n",[30,352,353],{"class":32,"line":140},[30,354,355],{"class":68},"}\n",[11,357,358],{},"albo użyj formy niezależnej od frameworka, która niczego nie potrzebuje:",[20,360,362],{"className":55,"code":361,"language":57,"meta":25,"style":25},"const claims = await authenticate(req, { issuer, audience })\n",[27,363,364],{"__ignoreMap":25},[30,365,366,368,371,373,376,379],{"class":32,"line":33},[30,367,107],{"class":64},[30,369,370],{"class":110}," claims",[30,372,114],{"class":64},[30,374,375],{"class":64}," await",[30,377,378],{"class":36}," authenticate",[30,380,381],{"class":68},"(req, { issuer, audience })\n",[11,383,384,387,388,391,392,395],{},[27,385,386],{},"authenticate()"," przyjmuje cokolwiek, co ma właściwość ",[27,389,390],{},"headers.authorization",",\nwięc działa z dowolnym frameworkiem w Node. ",[27,393,394],{},"KleoraRequest"," jest wyeksportowany\ndla kształtu, który middleware czyta i zapisuje.",[49,397,399,400],{"id":398},"_2-co-podać-jako-audience","2. Co podać jako ",[27,401,402],{},"audience",[11,404,405,406,409,410,413,414,417,418,421,422,425],{},"Claim ",[27,407,408],{},"aud"," tokenu dostępu to ",[27,411,412],{},"[client_id]",", a po ustawieniu na środowisku\nwłasnego ",[27,415,416],{},"api_audience"," — ",[27,419,420],{},"[client_id, api_audience]",". Świeże środowisko nie ma\ngo wcale, więc dopóki go nie ustawisz, podawaj ",[15,423,424],{},"identyfikator klienta",", a\npotem własny identyfikator API.",[49,427,429],{"id":428},"_3-trzy-odpowiedzi-jakie-może-mieć-odmowa","3. Trzy odpowiedzi, jakie może mieć odmowa",[431,432,433,446],"table",{},[434,435,436],"thead",{},[437,438,439,443],"tr",{},[440,441,442],"th",{},"Sytuacja",[440,444,445],{},"Odpowiedź",[447,448,449,468,482],"tbody",{},[437,450,451,459],{},[452,453,454,455,458],"td",{},"Brak nagłówka ",[27,456,457],{},"Authorization"," albo token, który się nie zweryfikował",[452,460,461,464,465],{},[27,462,463],{},"401",", dokument problemu ",[27,466,467],{},"unauthenticated",[437,469,470,473],{},[452,471,472],{},"Token poprawny, ale brakuje uprawnienia",[452,474,475,478,479],{},[27,476,477],{},"403"," ",[27,480,481],{},"insufficient-permission",[437,483,484,487],{},[452,485,486],{},"Zestawu kluczy issuera w ogóle nie da się odczytać",[452,488,489,478,492],{},[27,490,491],{},"503",[27,493,494],{},"issuer-unavailable",[11,496,497,498,500,501,505,506,509,510,512],{},"Treść ",[27,499,463],{}," mówi tylko tyle, że żądanie nie zostało uwierzytelnione. ",[502,503,504],"em",{},"Powód"," —\ntoken wygasł, zły audience, zły podpis — idzie do opcji ",[27,507,508],{},"logger",", czyli tam,\ngdzie przydaje się Tobie, a nie temu, kto próbuje zgadywać. ",[27,511,491],{}," znaczy więcej,\nniż wygląda: bez niego nasza awaria mówiłaby każdemu z Twoich użytkowników, że\njego token jest zły, i odsyłała go do issuera, który i tak już leży.",[11,514,515,516,519],{},"Wszystko inne to Twoja usługa skonfigurowana źle, i paczka nie wybiera za Ciebie\nstatusu: w Express dostaje to ",[27,517,518],{},"next(error)"," i odpowiada Twój własny handler\nbłędów.",[11,521,522,524,525,527,528,530,531,533],{},[27,523,209],{}," odpowiada ",[27,526,463],{},", a nie ",[27,529,477],{},", kiedy ",[27,532,259],{}," w ogóle\nnie ma — trasa, na której zabrakło middleware uwierzytelniającego, to nie\nproblem z uprawnieniami.",[49,535,537],{"id":536},"_4-co-dokładnie-sprawdza-weryfikator","4. Co dokładnie sprawdza weryfikator",[11,539,540,541,544,545,548,549,552,553,556,557,559,560,563,564,567,568,571],{},"Nagłówek ",[27,542,543],{},"typ"," równy ",[27,546,547],{},"at+jwt",", podpis ",[27,550,551],{},"ES256"," względem zestawu kluczy issuera,\n",[27,554,555],{},"iss"," dokładnie, ",[27,558,408],{}," zawierające Twój audience, ",[27,561,562],{},"exp"," i ",[27,565,566],{},"iat"," z tolerancją 60\nsekund oraz to, że claim ",[27,569,570],{},"env"," tokenu zgadza się ze skonfigurowanym issuerem —\nwięc token z sandboksa nie zadowoli po cichu produkcyjnego API.",[11,573,574,575,578,579,582,583,585],{},"Zestaw kluczy pobierany jest raz na issuer i trzymany tak długo, jak mówi jego\n",[27,576,577],{},"Cache-Control",". ",[27,580,581],{},"kid",", którego cache nie zna, wywołuje jedno ponowne pobranie,\nnie częściej niż raz na minutę; nieudane pobranie serwuje zapamiętany zestaw do\ndwukrotności jego TTL, a potem odmawia. Klucze podpisujące są publikowane, zanim\nzaczną być używane, więc rotacja w locie trafia na ",[27,584,581],{},", który już jest w\ncache'u.",[49,587,589],{"id":588},"fastify-i-cała-reszta","Fastify i cała reszta",[11,591,592,595,596,599],{},[27,593,594],{},"fastify({ issuer, audience })"," to ta sama wtyczka: dekoruje ",[27,597,598],{},"request.kleora"," i\ndaje te same trzy odpowiedzi. Rejestruje się bez enkapsulacji, bo zarejestrowanie\nwtyczki uwierzytelniającej w korzeniu nigdy nie znaczy „tylko trasy wewnątrz tej\nwtyczki”.",[11,601,602,603,606,607,612],{},"Dla API w Pythonie to samo sprawdzenie i te same reguły ma ",[27,604,605],{},"kleora"," —\nopisuje je ",[608,609,611],"a",{"href":610},"\u002Fpl\u002Fdocs\u002Fpierwsze-kroki\u002Finstalacja","Instalacja SDK",".",[614,615,616],"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 .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}",{"title":25,"searchDepth":97,"depth":97,"links":618},[619,620,622,623,624],{"id":51,"depth":78,"text":52},{"id":398,"depth":78,"text":621},"2. Co podać jako audience",{"id":428,"depth":78,"text":429},{"id":536,"depth":78,"text":537},{"id":588,"depth":78,"text":589},"Sprawdzanie tokenu dostępu we własnym API — middleware, uprawnienia i trzy odpowiedzi, jakie może mieć odmowa.","md","guides\u002Fquickstarts\u002Fexpress",{},40,"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fexpress",false,{"title":5,"description":625},"pl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fexpress","XveUetNU8_K5YPvhLFjPrP595uQTBD1L0cS2VVmQ7NU",[636,642,647,651,656,662,663,668,673,678,683,688,693,698],{"path":637,"title":638,"description":639,"order":640,"key":641},"\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":643,"title":644,"description":645,"order":175,"key":646},"\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":610,"title":611,"description":648,"order":649,"key":650},"Pięć paczek, które publikujemy, do czego służy każda z nich i jakiej konfiguracji wymaga.",20,"getting-started\u002Finstallation",{"path":652,"title":653,"description":654,"order":175,"key":655},"\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":657,"title":658,"description":659,"order":660,"key":661},"\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":630,"title":5,"description":625,"order":629,"key":627},{"path":664,"title":665,"description":666,"order":175,"key":667},"\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":669,"title":670,"description":671,"order":649,"key":672},"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fnextjs","Next.js","SDK przeglądarkowe w aplikacji na App Router — leniwy klient, trasa powrotna i sprawdzanie tokenu w route handlerze.","guides\u002Fquickstarts\u002Fnextjs",{"path":674,"title":675,"description":676,"order":660,"key":677},"\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":679,"title":680,"description":681,"order":175,"key":682},"\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":684,"title":685,"description":686,"order":649,"key":687},"\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":689,"title":690,"description":691,"order":649,"key":692},"\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":694,"title":695,"description":696,"order":660,"key":697},"\u002Fpl\u002Fdocs\u002Freferencje","Referencje","Materiały referencyjne dla API HTTP i paczek SDK.","reference\u002Findex",{"path":699,"title":700,"description":701,"order":175,"key":702},"\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",1790698198178]