[{"data":1,"prerenderedAt":1342},["ShallowReactive",2],{"docs:doc:\u002Fen\u002Fdocs\u002Fgetting-started\u002Finstallation":3,"docs:pages:docs_en":1273},{"id":4,"title":5,"alt":6,"body":7,"description":1263,"extension":1264,"key":1265,"meta":1266,"navigation":507,"order":1267,"path":1268,"placeholder":1269,"seo":1270,"stem":1271,"__hash__":1272},"docs_en\u002Fen\u002Fdocs\u002Fgetting-started\u002Finstallation.md","Installing an SDK","\u002Fpl\u002Fdocs\u002Fpierwsze-kroki\u002Finstalacja",{"type":8,"value":9,"toc":1254},"minimark",[10,28,31,121,127,130,155,161,294,303,308,315,329,640,650,653,734,768,773,776,790,822,828,831,846,984,987,991,1004,1018,1108,1217,1239,1243,1250],[11,12,15],"callout",{"title":13,"type":14},"The packages are not on the registries yet","warning",[16,17,18,19,23,24,27],"p",{},"Every package below is at version ",[20,21,22],"code",{},"0.1.0"," and is published to npm and PyPI with\nthe ",[20,25,26],{},"1.0.0"," release. The commands are the ones that will fetch them.",[16,29,30],{},"Five packages, all Apache-2.0, all optional: Kleora is an OAuth 2.1 and\nOpenID Connect provider, and a generic client library works against it.",[32,33,34,50],"table",{},[35,36,37],"thead",{},[38,39,40,44,47],"tr",{},[41,42,43],"th",{},"Package",[41,45,46],{},"What it is for",[41,48,49],{},"Needs",[51,52,53,67,80,93,106],"tbody",{},[38,54,55,61,64],{},[56,57,58],"td",{},[20,59,60],{},"@kleora-io\u002Fbrowser",[56,62,63],{},"Signing in from a single-page application.",[56,65,66],{},"Node 22+ to build; evergreen browsers",[38,68,69,74,77],{},[56,70,71],{},[20,72,73],{},"@kleora-io\u002Fnuxt",[56,75,76],{},"The same, as a Nuxt module.",[56,78,79],{},"Nuxt 4, Node 22+",[38,81,82,87,90],{},[56,83,84],{},[20,85,86],{},"@kleora-io\u002Fnode",[56,88,89],{},"Verifying access tokens on a Node API, and calling the management API.",[56,91,92],{},"Node 20+",[38,94,95,100,103],{},[56,96,97],{},[20,98,99],{},"kleora",[56,101,102],{},"The same, in Python.",[56,104,105],{},"Python 3.12+",[38,107,108,113,119],{},[56,109,110],{},[20,111,112],{},"django-kleora",[56,114,115,116,118],{},"The Django integration, over ",[20,117,99],{},".",[56,120,105],{},[122,123,125],"h2",{"id":124},"kleora-iobrowser",[20,126,60],{},[16,128,129],{},"The authorization-code-with-PKCE client. No runtime dependencies, ES modules\nonly.",[131,132,137],"pre",{"className":133,"code":134,"language":135,"meta":136,"style":136},"language-bash shiki shiki-themes github-light","npm install @kleora-io\u002Fbrowser\n","bash","",[20,138,139],{"__ignoreMap":136},[140,141,144,148,152],"span",{"class":142,"line":143},"line",1,[140,145,147],{"class":146},"s7eDp","npm",[140,149,151],{"class":150},"sYBdl"," install",[140,153,154],{"class":150}," @kleora-io\u002Fbrowser\n",[16,156,157,160],{},[20,158,159],{},"createKleora()"," takes three required options and four optional ones:",[32,162,163,176],{},[35,164,165],{},[38,166,167,170,173],{},[41,168,169],{},"Option",[41,171,172],{},"Default",[41,174,175],{},"Meaning",[51,177,178,195,215,227,246,258,270],{},[38,179,180,185,188],{},[56,181,182],{},[20,183,184],{},"issuer",[56,186,187],{},"—",[56,189,190,191,194],{},"The environment's address, e.g. ",[20,192,193],{},"https:\u002F\u002Facme.kleora.eu",". A trailing slash is normalised away.",[38,196,197,202,204],{},[56,198,199],{},[20,200,201],{},"clientId",[56,203,187],{},[56,205,206,207,210,211,214],{},"The client's ",[20,208,209],{},"client_id",". It must be a ",[20,212,213],{},"spa"," client.",[38,216,217,222,224],{},[56,218,219],{},[20,220,221],{},"redirectUri",[56,223,187],{},[56,225,226],{},"One of that client's registered redirect URIs, matched exactly.",[38,228,229,234,239],{},[56,230,231],{},[20,232,233],{},"scope",[56,235,236],{},[20,237,238],{},"openid profile email offline_access",[56,240,241,242,245],{},"Drop ",[20,243,244],{},"offline_access"," and no refresh token is issued, so the token cannot be renewed silently.",[38,247,248,253,255],{},[56,249,250],{},[20,251,252],{},"audience",[56,254,187],{},[56,256,257],{},"The environment's API audience, if you have set one.",[38,259,260,265,267],{},[56,261,262],{},[20,263,264],{},"tenant",[56,266,187],{},[56,268,269],{},"Pins every sign-in to one workspace slug.",[38,271,272,277,282],{},[56,273,274],{},[20,275,276],{},"storage",[56,278,279],{},[20,280,281],{},"memory",[56,283,284,286,287,290,291,118],{},[20,285,281],{}," or ",[20,288,289],{},"session",". Never ",[20,292,293],{},"localStorage",[16,295,296,297,302],{},"The ",[298,299,301],"a",{"href":300},"\u002Fen\u002Fdocs\u002Fgetting-started\u002Fquickstart","quickstart"," walks through a working\npage end to end.",[122,304,306],{"id":305},"kleora-ionuxt",[20,307,73],{},[16,309,310,311,314],{},"A Nuxt 4 module wrapping the browser SDK: auto-imported composables, a route\nguard, a callback page and an authenticated ",[20,312,313],{},"$fetch"," instance.",[131,316,318],{"className":133,"code":317,"language":135,"meta":136,"style":136},"npm install @kleora-io\u002Fnuxt\n",[20,319,320],{"__ignoreMap":136},[140,321,322,324,326],{"class":142,"line":143},[140,323,147],{"class":146},[140,325,151],{"class":150},[140,327,328],{"class":150}," @kleora-io\u002Fnuxt\n",[330,331,332,401],"code-tabs",{},[333,334,335],"template",{"v-slot:ts":136},[131,336,340],{"className":337,"code":338,"language":339,"meta":136,"style":136},"language-ts shiki shiki-themes github-light","\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: ['@kleora-io\u002Fnuxt'],\n  kleora: { issuer: '\u003Cissuer>', clientId: '\u003Cclient_id>' },\n})\n","ts",[20,341,342,348,365,377,395],{"__ignoreMap":136},[140,343,344],{"class":142,"line":143},[140,345,347],{"class":346},"sAwPA","\u002F\u002F nuxt.config.ts\n",[140,349,351,355,358,361],{"class":142,"line":350},2,[140,352,354],{"class":353},"sD7c4","export",[140,356,357],{"class":353}," default",[140,359,360],{"class":146}," defineNuxtConfig",[140,362,364],{"class":363},"sgsFI","({\n",[140,366,368,371,374],{"class":142,"line":367},3,[140,369,370],{"class":363},"  modules: [",[140,372,373],{"class":150},"'@kleora-io\u002Fnuxt'",[140,375,376],{"class":363},"],\n",[140,378,380,383,386,389,392],{"class":142,"line":379},4,[140,381,382],{"class":363},"  kleora: { issuer: ",[140,384,385],{"class":150},"'\u003Cissuer>'",[140,387,388],{"class":363},", clientId: ",[140,390,391],{"class":150},"'\u003Cclient_id>'",[140,393,394],{"class":363}," },\n",[140,396,398],{"class":142,"line":397},5,[140,399,400],{"class":363},"})\n",[333,402,403],{"v-slot:vue":136},[131,404,408],{"className":405,"code":406,"language":407,"meta":136,"style":136},"language-vue shiki shiki-themes github-light","\u003C!-- app\u002Fpages\u002Findex.vue -->\n\u003Cscript setup lang=\"ts\">\nconst { isAuthenticated, isLoading, login, logout } = useKleora()\nconst user = useKleoraUser()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp v-if=\"isLoading\">Loading…\u003C\u002Fp>\n  \u003Cbutton v-else-if=\"!isAuthenticated\" @click=\"login()\">Sign in\u003C\u002Fbutton>\n  \u003Cdiv v-else>\n    \u003Cp>{{ user?.sub }} · {{ user?.email }} · {{ user?.tenant }}\u003C\u002Fp>\n    \u003Cbutton @click=\"logout({ returnTo: '\u002F' })\">Sign out\u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n","vue",[20,409,410,415,439,478,493,502,509,518,541,572,585,600,621,631],{"__ignoreMap":136},[140,411,412],{"class":142,"line":143},[140,413,414],{"class":346},"\u003C!-- app\u002Fpages\u002Findex.vue -->\n",[140,416,417,420,424,427,430,433,436],{"class":142,"line":350},[140,418,419],{"class":363},"\u003C",[140,421,423],{"class":422},"shJU0","script",[140,425,426],{"class":146}," setup",[140,428,429],{"class":146}," lang",[140,431,432],{"class":363},"=",[140,434,435],{"class":150},"\"ts\"",[140,437,438],{"class":363},">\n",[140,440,441,444,447,451,454,457,459,462,464,467,470,472,475],{"class":142,"line":367},[140,442,443],{"class":353},"const",[140,445,446],{"class":363}," { ",[140,448,450],{"class":449},"sYu0t","isAuthenticated",[140,452,453],{"class":363},", ",[140,455,456],{"class":449},"isLoading",[140,458,453],{"class":363},[140,460,461],{"class":449},"login",[140,463,453],{"class":363},[140,465,466],{"class":449},"logout",[140,468,469],{"class":363}," } ",[140,471,432],{"class":353},[140,473,474],{"class":146}," useKleora",[140,476,477],{"class":363},"()\n",[140,479,480,482,485,488,491],{"class":142,"line":379},[140,481,443],{"class":353},[140,483,484],{"class":449}," user",[140,486,487],{"class":353}," =",[140,489,490],{"class":146}," useKleoraUser",[140,492,477],{"class":363},[140,494,495,498,500],{"class":142,"line":397},[140,496,497],{"class":363},"\u003C\u002F",[140,499,423],{"class":422},[140,501,438],{"class":363},[140,503,505],{"class":142,"line":504},6,[140,506,508],{"emptyLinePlaceholder":507},true,"\n",[140,510,512,514,516],{"class":142,"line":511},7,[140,513,419],{"class":363},[140,515,333],{"class":422},[140,517,438],{"class":363},[140,519,521,524,526,529,531,534,537,539],{"class":142,"line":520},8,[140,522,523],{"class":363},"  \u003C",[140,525,16],{"class":422},[140,527,528],{"class":146}," v-if",[140,530,432],{"class":363},[140,532,533],{"class":150},"\"isLoading\"",[140,535,536],{"class":363},">Loading…\u003C\u002F",[140,538,16],{"class":422},[140,540,438],{"class":363},[140,542,544,546,549,552,554,557,560,562,565,568,570],{"class":142,"line":543},9,[140,545,523],{"class":363},[140,547,548],{"class":422},"button",[140,550,551],{"class":146}," v-else-if",[140,553,432],{"class":363},[140,555,556],{"class":150},"\"!isAuthenticated\"",[140,558,559],{"class":146}," @click",[140,561,432],{"class":363},[140,563,564],{"class":150},"\"login()\"",[140,566,567],{"class":363},">Sign in\u003C\u002F",[140,569,548],{"class":422},[140,571,438],{"class":363},[140,573,575,577,580,583],{"class":142,"line":574},10,[140,576,523],{"class":363},[140,578,579],{"class":422},"div",[140,581,582],{"class":146}," v-else",[140,584,438],{"class":363},[140,586,588,591,593,596,598],{"class":142,"line":587},11,[140,589,590],{"class":363},"    \u003C",[140,592,16],{"class":422},[140,594,595],{"class":363},">{{ user?.sub }} · {{ user?.email }} · {{ user?.tenant }}\u003C\u002F",[140,597,16],{"class":422},[140,599,438],{"class":363},[140,601,603,605,607,609,611,614,617,619],{"class":142,"line":602},12,[140,604,590],{"class":363},[140,606,548],{"class":422},[140,608,559],{"class":146},[140,610,432],{"class":363},[140,612,613],{"class":150},"\"logout({ returnTo: '\u002F' })\"",[140,615,616],{"class":363},">Sign out\u003C\u002F",[140,618,548],{"class":422},[140,620,438],{"class":363},[140,622,624,627,629],{"class":142,"line":623},13,[140,625,626],{"class":363},"  \u003C\u002F",[140,628,579],{"class":422},[140,630,438],{"class":363},[140,632,634,636,638],{"class":142,"line":633},14,[140,635,497],{"class":363},[140,637,333],{"class":422},[140,639,438],{"class":363},[16,641,642,643,645,646,649],{},"There is no ",[20,644,221],{}," in that config because the module defaults it to its\nown callback page on the site's own origin — ",[20,647,648],{},"\u002Fauth\u002Fcallback",". That is still the\nURI you register on the client.",[16,651,652],{},"What the module gives you:",[32,654,655,665],{},[35,656,657],{},[38,658,659,662],{},[41,660,661],{},"Export",[41,663,664],{},"What it is",[51,666,667,679,696,711,725],{},[38,668,669,674],{},[56,670,671],{},[20,672,673],{},"useKleora()",[56,675,676,118],{},[20,677,678],{},"{ client, user, isAuthenticated, isLoading, hasLikelySession, login, logout, getAccessToken, restore, settle }",[38,680,681,686],{},[56,682,683],{},[20,684,685],{},"useKleoraUser()",[56,687,688,689,692,693,118],{},"A ",[20,690,691],{},"Ref"," holding the signed-in user, or ",[20,694,695],{},"null",[38,697,698,706],{},[56,699,700,453,703],{},[20,701,702],{},"useKleoraApi()",[20,704,705],{},"$kleoraApi",[56,707,688,708,710],{},[20,709,313],{}," instance with your API's base URL and a bearer token on every request.",[38,712,713,719],{},[56,714,715,716],{},"middleware ",[20,717,718],{},"kleora-auth",[56,720,721,722,118],{},"Registered globally, acts only on pages that declare ",[20,723,724],{},"definePageMeta({ auth: true })",[38,726,727,731],{},[56,728,729],{},[20,730,648],{},[56,732,733],{},"A page the module adds, which completes the sign-in and returns you where you started.",[16,735,736,737,453,740,453,743,453,746,749,750,453,753,453,756,759,760,763,764,767],{},"Every option can come from the environment instead of the config file:\n",[20,738,739],{},"NUXT_PUBLIC_KLEORA_ISSUER",[20,741,742],{},"…_CLIENT_ID",[20,744,745],{},"…_REDIRECT_URI",[20,747,748],{},"…_SCOPE",",\n",[20,751,752],{},"…_AUDIENCE",[20,754,755],{},"…_TENANT",[20,757,758],{},"…_STORAGE",", and ",[20,761,762],{},"NUXT_PUBLIC_API_BASE"," for the API\nbase URL. The one exception is ",[20,765,766],{},"callbackPath",", which decides where the callback\npage is mounted and is therefore fixed at build time.",[122,769,771],{"id":770},"kleora-ionode",[20,772,86],{},[16,774,775],{},"Access-token verification with a JWKS cache, Express and Fastify middleware, and\na typed client for the management API. Its only runtime dependency is a JOSE\nlibrary; Express and Fastify are optional peers.",[131,777,779],{"className":133,"code":778,"language":135,"meta":136,"style":136},"npm install @kleora-io\u002Fnode\n",[20,780,781],{"__ignoreMap":136},[140,782,783,785,787],{"class":142,"line":143},[140,784,147],{"class":146},[140,786,151],{"class":150},[140,788,789],{"class":150}," @kleora-io\u002Fnode\n",[16,791,792,793,796,797,800,801,804,805,453,808,453,810,749,813,453,816,453,818,821],{},"Verification is framework-free — ",[20,794,795],{},"authenticate()"," takes anything with a\n",[20,798,799],{},"headers.authorization"," property — and ",[20,802,803],{},"verifyAccessToken()"," takes the bare\nstring. Both return the token's claims: ",[20,806,807],{},"sub",[20,809,264],{},[20,811,812],{},"roles",[20,814,815],{},"permissions",[20,817,233],{},[20,819,820],{},"env"," and the rest.",[122,823,825,827],{"id":824},"kleora-python",[20,826,99],{}," (Python)",[16,829,830],{},"The same verification in Python, plus a management client in both a synchronous\nand an asynchronous flavour.",[131,832,834],{"className":133,"code":833,"language":135,"meta":136,"style":136},"pip install kleora\n",[20,835,836],{"__ignoreMap":136},[140,837,838,841,843],{"class":142,"line":143},[140,839,840],{"class":146},"pip",[140,842,151],{"class":150},[140,844,845],{"class":150}," kleora\n",[330,847,848,914],{},[333,849,850],{"v-slot:ts":136},[131,851,853],{"className":337,"code":852,"language":339,"meta":136,"style":136},"import { verifyAccessToken } from '@kleora-io\u002Fnode'\n\nconst claims = await verifyAccessToken(token, {\n  issuer: 'https:\u002F\u002Facme.sandbox.kleora.eu',\n  audience: '\u003Cclient_id>',\n})\n",[20,854,855,869,873,891,901,910],{"__ignoreMap":136},[140,856,857,860,863,866],{"class":142,"line":143},[140,858,859],{"class":353},"import",[140,861,862],{"class":363}," { verifyAccessToken } ",[140,864,865],{"class":353},"from",[140,867,868],{"class":150}," '@kleora-io\u002Fnode'\n",[140,870,871],{"class":142,"line":350},[140,872,508],{"emptyLinePlaceholder":507},[140,874,875,877,880,882,885,888],{"class":142,"line":367},[140,876,443],{"class":353},[140,878,879],{"class":449}," claims",[140,881,487],{"class":353},[140,883,884],{"class":353}," await",[140,886,887],{"class":146}," verifyAccessToken",[140,889,890],{"class":363},"(token, {\n",[140,892,893,896,899],{"class":142,"line":379},[140,894,895],{"class":363},"  issuer: ",[140,897,898],{"class":150},"'https:\u002F\u002Facme.sandbox.kleora.eu'",[140,900,749],{"class":363},[140,902,903,906,908],{"class":142,"line":397},[140,904,905],{"class":363},"  audience: ",[140,907,391],{"class":150},[140,909,749],{"class":363},[140,911,912],{"class":142,"line":504},[140,913,400],{"class":363},[333,915,916],{"v-slot:python":136},[131,917,921],{"className":918,"code":919,"language":920,"meta":136,"style":136},"language-python shiki shiki-themes github-light","from kleora import verify_access_token\n\nclaims = verify_access_token(\n    token,\n    issuer=\"https:\u002F\u002Facme.sandbox.kleora.eu\",\n    audience=\"\u003Cclient_id>\",\n)\n","python",[20,922,923,935,939,949,954,967,979],{"__ignoreMap":136},[140,924,925,927,930,932],{"class":142,"line":143},[140,926,865],{"class":353},[140,928,929],{"class":363}," kleora ",[140,931,859],{"class":353},[140,933,934],{"class":363}," verify_access_token\n",[140,936,937],{"class":142,"line":350},[140,938,508],{"emptyLinePlaceholder":507},[140,940,941,944,946],{"class":142,"line":367},[140,942,943],{"class":363},"claims ",[140,945,432],{"class":353},[140,947,948],{"class":363}," verify_access_token(\n",[140,950,951],{"class":142,"line":379},[140,952,953],{"class":363},"    token,\n",[140,955,956,960,962,965],{"class":142,"line":397},[140,957,959],{"class":958},"sqxcx","    issuer",[140,961,432],{"class":353},[140,963,964],{"class":150},"\"https:\u002F\u002Facme.sandbox.kleora.eu\"",[140,966,749],{"class":363},[140,968,969,972,974,977],{"class":142,"line":504},[140,970,971],{"class":958},"    audience",[140,973,432],{"class":353},[140,975,976],{"class":150},"\"\u003Cclient_id>\"",[140,978,749],{"class":363},[140,980,981],{"class":142,"line":511},[140,982,983],{"class":363},")\n",[16,985,986],{},"Both fetch your environment's key set once, cache it for as long as the\nresponse says to, and fail closed if it becomes unreachable. Neither reads an\nenvironment variable: the issuer, the audience and any API key are passed in by\nyou.",[122,988,989],{"id":112},[20,990,112],{},[16,992,993,994,997,998,1000,1001,1003],{},"A django-ninja authentication class, a middleware, a permission decorator and a\n",[20,995,996],{},"KLEORA"," settings dict, over ",[20,999,99],{},", which it installs for you. Nothing in it\nverifies a token itself: the key-set cache and the token errors are ",[20,1002,99],{},"'s.",[131,1005,1007],{"className":133,"code":1006,"language":135,"meta":136,"style":136},"pip install django-kleora\n",[20,1008,1009],{"__ignoreMap":136},[140,1010,1011,1013,1015],{"class":142,"line":143},[140,1012,840],{"class":146},[140,1014,151],{"class":150},[140,1016,1017],{"class":150}," django-kleora\n",[131,1019,1021],{"className":918,"code":1020,"language":920,"meta":136,"style":136},"# settings.py\nINSTALLED_APPS = [..., \"django_kleora\"]\nMIDDLEWARE = [..., \"django_kleora.KleoraAuthMiddleware\"]\n\nKLEORA = {\n    \"ISSUER\": \"https:\u002F\u002Facme.sandbox.kleora.eu\",\n    \"AUDIENCE\": \"\u003Cclient_id>\",\n}\n",[20,1022,1023,1028,1049,1067,1071,1080,1092,1103],{"__ignoreMap":136},[140,1024,1025],{"class":142,"line":143},[140,1026,1027],{"class":346},"# settings.py\n",[140,1029,1030,1033,1035,1038,1041,1043,1046],{"class":142,"line":350},[140,1031,1032],{"class":449},"INSTALLED_APPS",[140,1034,487],{"class":353},[140,1036,1037],{"class":363}," [",[140,1039,1040],{"class":449},"...",[140,1042,453],{"class":363},[140,1044,1045],{"class":150},"\"django_kleora\"",[140,1047,1048],{"class":363},"]\n",[140,1050,1051,1054,1056,1058,1060,1062,1065],{"class":142,"line":367},[140,1052,1053],{"class":449},"MIDDLEWARE",[140,1055,487],{"class":353},[140,1057,1037],{"class":363},[140,1059,1040],{"class":449},[140,1061,453],{"class":363},[140,1063,1064],{"class":150},"\"django_kleora.KleoraAuthMiddleware\"",[140,1066,1048],{"class":363},[140,1068,1069],{"class":142,"line":379},[140,1070,508],{"emptyLinePlaceholder":507},[140,1072,1073,1075,1077],{"class":142,"line":397},[140,1074,996],{"class":449},[140,1076,487],{"class":353},[140,1078,1079],{"class":363}," {\n",[140,1081,1082,1085,1088,1090],{"class":142,"line":504},[140,1083,1084],{"class":150},"    \"ISSUER\"",[140,1086,1087],{"class":363},": ",[140,1089,964],{"class":150},[140,1091,749],{"class":363},[140,1093,1094,1097,1099,1101],{"class":142,"line":511},[140,1095,1096],{"class":150},"    \"AUDIENCE\"",[140,1098,1087],{"class":363},[140,1100,976],{"class":150},[140,1102,749],{"class":363},[140,1104,1105],{"class":142,"line":520},[140,1106,1107],{"class":363},"}\n",[131,1109,1111],{"className":918,"code":1110,"language":920,"meta":136,"style":136},"from django_kleora import KleoraAuthentication, requires_permission\nfrom ninja import NinjaAPI\n\napi = NinjaAPI(auth=KleoraAuthentication())\n\n\n@api.get(\"\u002Finvoices\")\n@requires_permission(\"invoices:read\")\ndef invoices(request):\n    return {\"tenant\": request.kleora.tenant}\n",[20,1112,1113,1125,1137,1141,1159,1163,1167,1180,1192,1203],{"__ignoreMap":136},[140,1114,1115,1117,1120,1122],{"class":142,"line":143},[140,1116,865],{"class":353},[140,1118,1119],{"class":363}," django_kleora ",[140,1121,859],{"class":353},[140,1123,1124],{"class":363}," KleoraAuthentication, requires_permission\n",[140,1126,1127,1129,1132,1134],{"class":142,"line":350},[140,1128,865],{"class":353},[140,1130,1131],{"class":363}," ninja ",[140,1133,859],{"class":353},[140,1135,1136],{"class":363}," NinjaAPI\n",[140,1138,1139],{"class":142,"line":367},[140,1140,508],{"emptyLinePlaceholder":507},[140,1142,1143,1146,1148,1151,1154,1156],{"class":142,"line":379},[140,1144,1145],{"class":363},"api ",[140,1147,432],{"class":353},[140,1149,1150],{"class":363}," NinjaAPI(",[140,1152,1153],{"class":958},"auth",[140,1155,432],{"class":353},[140,1157,1158],{"class":363},"KleoraAuthentication())\n",[140,1160,1161],{"class":142,"line":397},[140,1162,508],{"emptyLinePlaceholder":507},[140,1164,1165],{"class":142,"line":504},[140,1166,508],{"emptyLinePlaceholder":507},[140,1168,1169,1172,1175,1178],{"class":142,"line":511},[140,1170,1171],{"class":146},"@api.get",[140,1173,1174],{"class":363},"(",[140,1176,1177],{"class":150},"\"\u002Finvoices\"",[140,1179,983],{"class":363},[140,1181,1182,1185,1187,1190],{"class":142,"line":520},[140,1183,1184],{"class":146},"@requires_permission",[140,1186,1174],{"class":363},[140,1188,1189],{"class":150},"\"invoices:read\"",[140,1191,983],{"class":363},[140,1193,1194,1197,1200],{"class":142,"line":543},[140,1195,1196],{"class":353},"def",[140,1198,1199],{"class":146}," invoices",[140,1201,1202],{"class":363},"(request):\n",[140,1204,1205,1208,1211,1214],{"class":142,"line":574},[140,1206,1207],{"class":353},"    return",[140,1209,1210],{"class":363}," {",[140,1212,1213],{"class":150},"\"tenant\"",[140,1215,1216],{"class":363},": request.kleora.tenant}\n",[16,1218,1219,1222,1223,1226,1227,1230,1231,1234,1235,1238],{},[20,1220,1221],{},"requires_permission"," answers ",[20,1224,1225],{},"401"," when there is no verified token, ",[20,1228,1229],{},"403"," when\nthe token lacks the permission, and ",[20,1232,1233],{},"503"," when the issuer's key set could not be\nread — the same three answers, and the same problem documents, as the Node\nmiddleware. The middleware is for views django-ninja never sees: it sets\n",[20,1236,1237],{},"request.kleora"," and never refuses a request itself.",[122,1240,1242],{"id":1241},"no-sdk-at-all","No SDK at all",[16,1244,1245,1246,1249],{},"The hosted pages and the OAuth endpoints are standard. Everything a generic\nOpenID Connect client needs is at\n",[20,1247,1248],{},"{issuer}\u002F.well-known\u002Fopenid-configuration",", and the key set it names is what\nverifies a token. Authorization code with PKCE is the only browser flow we\nsupport; implicit and password grants are not offered.",[1251,1252,1253],"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 .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}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":136,"searchDepth":367,"depth":367,"links":1255},[1256,1257,1258,1259,1261,1262],{"id":124,"depth":350,"text":60},{"id":305,"depth":350,"text":73},{"id":770,"depth":350,"text":86},{"id":824,"depth":350,"text":1260},"kleora (Python)",{"id":112,"depth":350,"text":112},{"id":1241,"depth":350,"text":1242},"The five packages we publish, what each one is for, and the configuration each one takes.","md","getting-started\u002Finstallation",{},20,"\u002Fen\u002Fdocs\u002Fgetting-started\u002Finstallation",false,{"title":5,"description":1263},"en\u002Fdocs\u002Fgetting-started\u002Finstallation","IwlRqEFQJf_FDgvhj5TJMZZCdP6a-41QZ_8XyTyuCWQ",[1274,1279,1280,1284,1290,1295,1300,1305,1311,1316,1321,1326,1332,1337],{"path":1275,"title":1276,"description":1277,"order":574,"key":1278},"\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":1268,"title":5,"description":1263,"order":1267,"key":1265},{"path":300,"title":1281,"description":1282,"order":574,"key":1283},"Quickstart","Create an App, wire up the browser SDK, and sign in for the first time — entirely in sandbox.","getting-started\u002Fquickstart",{"path":1285,"title":1286,"description":1287,"order":1288,"key":1289},"\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":1291,"title":1292,"description":1293,"order":1267,"key":1294},"\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":1296,"title":1297,"description":1298,"order":1267,"key":1299},"\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":1301,"title":1302,"description":1303,"order":574,"key":1304},"\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":1306,"title":1307,"description":1308,"order":1309,"key":1310},"\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":1312,"title":1313,"description":1314,"order":574,"key":1315},"\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":1317,"title":1318,"description":1319,"order":1267,"key":1320},"\u002Fen\u002Fdocs\u002Fguides\u002Fquickstarts\u002Fnextjs","Next.js","The browser SDK in an App Router application — a lazy client, a callback route, and token verification in a route handler.","guides\u002Fquickstarts\u002Fnextjs",{"path":1322,"title":1323,"description":1324,"order":1288,"key":1325},"\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":1327,"title":1328,"description":1329,"order":1330,"key":1331},"\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":1333,"title":1334,"description":1335,"order":574,"key":1336},"\u002Fen\u002Fdocs\u002Freference\u002Fcode-highlighting","Code highlighting","One fence per preloaded Shiki grammar — a fixture, not a reference page.","reference\u002Fcode-highlighting",{"path":1338,"title":1339,"description":1340,"order":1288,"key":1341},"\u002Fen\u002Fdocs\u002Freference","Reference","Reference material for the HTTP APIs and the SDK packages.","reference\u002Findex",1790698197372]