[{"data":1,"prerenderedAt":888},["ShallowReactive",2],{"docs:doc:\u002Fen\u002Fdocs\u002Fguides\u002Fquickstarts\u002Fnuxt":3,"docs:pages:docs_en":818},{"id":4,"title":5,"alt":6,"body":7,"description":808,"extension":809,"key":810,"meta":811,"navigation":377,"order":812,"path":813,"placeholder":814,"seo":815,"stem":816,"__hash__":817},"docs_en\u002Fen\u002Fdocs\u002Fguides\u002Fquickstarts\u002Fnuxt.md","Nuxt","\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fnuxt",{"type":8,"value":9,"toc":801},"minimark",[10,22,47,52,118,139,142,255,269,273,511,521,525,532,575,578,582,624,644,648,772,797],[11,12,13,17,18,21],"p",{},[14,15,16],"code",{},"@kleora-io\u002Fnuxt"," is the browser SDK as a Nuxt 4 module: it owns the client,\nadds the callback page, registers a route middleware and gives you an\nauthenticated ",[14,19,20],{},"$fetch",".",[23,24,29],"pre",{"className":25,"code":26,"language":27,"meta":28,"style":28},"language-bash shiki shiki-themes github-light","npm install @kleora-io\u002Fnuxt\n","bash","",[14,30,31],{"__ignoreMap":28},[32,33,36,40,44],"span",{"class":34,"line":35},"line",1,[32,37,39],{"class":38},"s7eDp","npm",[32,41,43],{"class":42},"sYBdl"," install",[32,45,46],{"class":42}," @kleora-io\u002Fnuxt\n",[48,49,51],"h2",{"id":50},"_1-register-the-module","1. Register the module",[23,53,57],{"className":54,"code":55,"language":56,"meta":28,"style":28},"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",[14,58,59,65,82,94,112],{"__ignoreMap":28},[32,60,61],{"class":34,"line":35},[32,62,64],{"class":63},"sAwPA","\u002F\u002F nuxt.config.ts\n",[32,66,68,72,75,78],{"class":34,"line":67},2,[32,69,71],{"class":70},"sD7c4","export",[32,73,74],{"class":70}," default",[32,76,77],{"class":38}," defineNuxtConfig",[32,79,81],{"class":80},"sgsFI","({\n",[32,83,85,88,91],{"class":34,"line":84},3,[32,86,87],{"class":80},"  modules: [",[32,89,90],{"class":42},"'@kleora-io\u002Fnuxt'",[32,92,93],{"class":80},"],\n",[32,95,97,100,103,106,109],{"class":34,"line":96},4,[32,98,99],{"class":80},"  kleora: { issuer: ",[32,101,102],{"class":42},"'\u003Cissuer>'",[32,104,105],{"class":80},", clientId: ",[32,107,108],{"class":42},"'\u003Cclient_id>'",[32,110,111],{"class":80}," },\n",[32,113,115],{"class":34,"line":114},5,[32,116,117],{"class":80},"})\n",[11,119,120,123,124,127,128,131,132,138],{},[14,121,122],{},"issuer"," and ",[14,125,126],{},"clientId"," are the only required options — a blank one throws as\nthe application boots, naming both the option and the environment variable that\nwould have set it. There is no ",[14,129,130],{},"redirectUri"," here because the module defaults it to its own\ncallback page on the origin the site is served from: ",[133,134,135],"strong",{},[14,136,137],{},"\u002Fauth\u002Fcallback",". That\nis still the URI you register on the client, exactly as written.",[11,140,141],{},"Eight options can come from the environment instead of the config file, which\nis how one build serves sandbox and production:",[143,144,145,158],"table",{},[146,147,148],"thead",{},[149,150,151,155],"tr",{},[152,153,154],"th",{},"Option",[152,156,157],{},"Variable",[159,160,161,173,184,195,207,219,231,243],"tbody",{},[149,162,163,168],{},[164,165,166],"td",{},[14,167,122],{},[164,169,170],{},[14,171,172],{},"NUXT_PUBLIC_KLEORA_ISSUER",[149,174,175,179],{},[164,176,177],{},[14,178,126],{},[164,180,181],{},[14,182,183],{},"NUXT_PUBLIC_KLEORA_CLIENT_ID",[149,185,186,190],{},[164,187,188],{},[14,189,130],{},[164,191,192],{},[14,193,194],{},"NUXT_PUBLIC_KLEORA_REDIRECT_URI",[149,196,197,202],{},[164,198,199],{},[14,200,201],{},"scope",[164,203,204],{},[14,205,206],{},"NUXT_PUBLIC_KLEORA_SCOPE",[149,208,209,214],{},[164,210,211],{},[14,212,213],{},"audience",[164,215,216],{},[14,217,218],{},"NUXT_PUBLIC_KLEORA_AUDIENCE",[149,220,221,226],{},[164,222,223],{},[14,224,225],{},"tenant",[164,227,228],{},[14,229,230],{},"NUXT_PUBLIC_KLEORA_TENANT",[149,232,233,238],{},[164,234,235],{},[14,236,237],{},"storage",[164,239,240],{},[14,241,242],{},"NUXT_PUBLIC_KLEORA_STORAGE",[149,244,245,250],{},[164,246,247],{},[14,248,249],{},"apiBase",[164,251,252],{},[14,253,254],{},"NUXT_PUBLIC_API_BASE",[11,256,257,258,261,262,265,266,268],{},"The ninth, ",[14,259,260],{},"callbackPath",", is build-time only: the module mounts a route there\nwhile building, so an environment variable would move where the browser lands\nwithout moving the page that handles it. Set it in ",[14,263,264],{},"nuxt.config",", and keep it\nequal to ",[14,267,130],{},"'s path.",[48,270,272],{"id":271},"_2-sign-in-from-a-page","2. Sign in from a page",[23,274,278],{"className":275,"code":276,"language":277,"meta":28,"style":28},"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",[14,279,280,285,309,348,363,372,379,389,412,443,456,471,492,502],{"__ignoreMap":28},[32,281,282],{"class":34,"line":35},[32,283,284],{"class":63},"\u003C!-- app\u002Fpages\u002Findex.vue -->\n",[32,286,287,290,294,297,300,303,306],{"class":34,"line":67},[32,288,289],{"class":80},"\u003C",[32,291,293],{"class":292},"shJU0","script",[32,295,296],{"class":38}," setup",[32,298,299],{"class":38}," lang",[32,301,302],{"class":80},"=",[32,304,305],{"class":42},"\"ts\"",[32,307,308],{"class":80},">\n",[32,310,311,314,317,321,324,327,329,332,334,337,340,342,345],{"class":34,"line":84},[32,312,313],{"class":70},"const",[32,315,316],{"class":80}," { ",[32,318,320],{"class":319},"sYu0t","isAuthenticated",[32,322,323],{"class":80},", ",[32,325,326],{"class":319},"isLoading",[32,328,323],{"class":80},[32,330,331],{"class":319},"login",[32,333,323],{"class":80},[32,335,336],{"class":319},"logout",[32,338,339],{"class":80}," } ",[32,341,302],{"class":70},[32,343,344],{"class":38}," useKleora",[32,346,347],{"class":80},"()\n",[32,349,350,352,355,358,361],{"class":34,"line":96},[32,351,313],{"class":70},[32,353,354],{"class":319}," user",[32,356,357],{"class":70}," =",[32,359,360],{"class":38}," useKleoraUser",[32,362,347],{"class":80},[32,364,365,368,370],{"class":34,"line":114},[32,366,367],{"class":80},"\u003C\u002F",[32,369,293],{"class":292},[32,371,308],{"class":80},[32,373,375],{"class":34,"line":374},6,[32,376,378],{"emptyLinePlaceholder":377},true,"\n",[32,380,382,384,387],{"class":34,"line":381},7,[32,383,289],{"class":80},[32,385,386],{"class":292},"template",[32,388,308],{"class":80},[32,390,392,395,397,400,402,405,408,410],{"class":34,"line":391},8,[32,393,394],{"class":80},"  \u003C",[32,396,11],{"class":292},[32,398,399],{"class":38}," v-if",[32,401,302],{"class":80},[32,403,404],{"class":42},"\"isLoading\"",[32,406,407],{"class":80},">Loading…\u003C\u002F",[32,409,11],{"class":292},[32,411,308],{"class":80},[32,413,415,417,420,423,425,428,431,433,436,439,441],{"class":34,"line":414},9,[32,416,394],{"class":80},[32,418,419],{"class":292},"button",[32,421,422],{"class":38}," v-else-if",[32,424,302],{"class":80},[32,426,427],{"class":42},"\"!isAuthenticated\"",[32,429,430],{"class":38}," @click",[32,432,302],{"class":80},[32,434,435],{"class":42},"\"login()\"",[32,437,438],{"class":80},">Sign in\u003C\u002F",[32,440,419],{"class":292},[32,442,308],{"class":80},[32,444,446,448,451,454],{"class":34,"line":445},10,[32,447,394],{"class":80},[32,449,450],{"class":292},"div",[32,452,453],{"class":38}," v-else",[32,455,308],{"class":80},[32,457,459,462,464,467,469],{"class":34,"line":458},11,[32,460,461],{"class":80},"    \u003C",[32,463,11],{"class":292},[32,465,466],{"class":80},">{{ user?.sub }} · {{ user?.email }} · {{ user?.tenant }}\u003C\u002F",[32,468,11],{"class":292},[32,470,308],{"class":80},[32,472,474,476,478,480,482,485,488,490],{"class":34,"line":473},12,[32,475,461],{"class":80},[32,477,419],{"class":292},[32,479,430],{"class":38},[32,481,302],{"class":80},[32,483,484],{"class":42},"\"logout({ returnTo: '\u002F' })\"",[32,486,487],{"class":80},">Sign out\u003C\u002F",[32,489,419],{"class":292},[32,491,308],{"class":80},[32,493,495,498,500],{"class":34,"line":494},13,[32,496,497],{"class":80},"  \u003C\u002F",[32,499,450],{"class":292},[32,501,308],{"class":80},[32,503,505,507,509],{"class":34,"line":504},14,[32,506,367],{"class":80},[32,508,386],{"class":292},[32,510,308],{"class":80},[11,512,513,516,517,520],{},[14,514,515],{},"logout()"," with no argument sends the browser to the site root, so ",[14,518,519],{},"\u002F"," on your\norigin has to be one of the client's registered post-logout redirect URIs.",[48,522,524],{"id":523},"_3-protect-a-page","3. Protect a page",[11,526,527,528,531],{},"The module registers a global middleware named ",[14,529,530],{},"kleora-auth",", and it acts only\non pages that ask for it:",[23,533,535],{"className":275,"code":534,"language":277,"meta":28,"style":28},"\u003Cscript setup lang=\"ts\">\ndefinePageMeta({ auth: true })\n\u003C\u002Fscript>\n",[14,536,537,553,567],{"__ignoreMap":28},[32,538,539,541,543,545,547,549,551],{"class":34,"line":35},[32,540,289],{"class":80},[32,542,293],{"class":292},[32,544,296],{"class":38},[32,546,299],{"class":38},[32,548,302],{"class":80},[32,550,305],{"class":42},[32,552,308],{"class":80},[32,554,555,558,561,564],{"class":34,"line":67},[32,556,557],{"class":38},"definePageMeta",[32,559,560],{"class":80},"({ auth: ",[32,562,563],{"class":319},"true",[32,565,566],{"class":80}," })\n",[32,568,569,571,573],{"class":34,"line":84},[32,570,367],{"class":80},[32,572,293],{"class":292},[32,574,308],{"class":80},[11,576,577],{},"The protection lives in the page's own meta, so a page cannot lose it by being\nleft out of a list somewhere else. The middleware is client-side: server\nrendering produces the unauthenticated shell and never redirects.",[48,579,581],{"id":580},"_4-call-an-api-with-the-token-attached","4. Call an API with the token attached",[23,583,585],{"className":54,"code":584,"language":56,"meta":28,"style":28},"const api = useKleoraApi()\nconst invoices = await api('\u002Finvoices')\n",[14,586,587,601],{"__ignoreMap":28},[32,588,589,591,594,596,599],{"class":34,"line":35},[32,590,313],{"class":70},[32,592,593],{"class":319}," api",[32,595,357],{"class":70},[32,597,598],{"class":38}," useKleoraApi",[32,600,347],{"class":80},[32,602,603,605,608,610,613,615,618,621],{"class":34,"line":67},[32,604,313],{"class":70},[32,606,607],{"class":319}," invoices",[32,609,357],{"class":70},[32,611,612],{"class":70}," await",[32,614,593],{"class":38},[32,616,617],{"class":80},"(",[32,619,620],{"class":42},"'\u002Finvoices'",[32,622,623],{"class":80},")\n",[11,625,626,629,630,632,633,635,636,639,640,643],{},[14,627,628],{},"$kleoraApi"," is a ",[14,631,20],{}," instance with ",[14,634,249],{}," as its base URL and a\nbearer token on every request. A ",[14,637,638],{},"401"," is retried exactly once with a forced\nrefresh — so a token that was rejected is never simply sent again — and a\nfailure arrives as an ",[14,641,642],{},"ApiError"," carrying the RFC 9457 problem document, not as\na bare status code.",[48,645,647],{"id":646},"what-the-module-adds-for-you","What the module adds for you",[143,649,650,660],{},[146,651,652],{},[149,653,654,657],{},[152,655,656],{},"Export",[152,658,659],{},"What it is",[159,661,662,698,715,728,750,763],{},[149,663,664,669],{},[164,665,666],{},[14,667,668],{},"useKleora()",[164,670,671,672,323,675,323,678,323,680,323,682,323,685,323,687,323,689,323,692,323,695,21],{},"The context: ",[14,673,674],{},"client",[14,676,677],{},"user",[14,679,320],{},[14,681,326],{},[14,683,684],{},"hasLikelySession",[14,686,331],{},[14,688,336],{},[14,690,691],{},"getAccessToken",[14,693,694],{},"restore",[14,696,697],{},"settle",[149,699,700,705],{},[164,701,702],{},[14,703,704],{},"useKleoraUser()",[164,706,707,708,711,712,21],{},"A ",[14,709,710],{},"Ref"," holding the signed-in user, or ",[14,713,714],{},"null",[149,716,717,722],{},[164,718,719],{},[14,720,721],{},"useKleoraApi()",[164,723,724,725,727],{},"The authenticated ",[14,726,20],{}," instance, as a composable.",[149,729,730,735],{},[164,731,732],{},[14,733,734],{},"decideSessionRender()",[164,736,737,738,741,742,745,746,749],{},"Maps ",[14,739,740],{},"{ hasMarker, isLoading, isAuthenticated }"," onto ",[14,743,744],{},"'shell'"," or ",[14,747,748],{},"'redirect'",", for a layout that would otherwise flash.",[149,751,752,757],{},[164,753,754,755],{},"middleware ",[14,756,530],{},[164,758,759,760,21],{},"Global, keyed on ",[14,761,762],{},"definePageMeta({ auth: true })",[149,764,765,769],{},[164,766,767],{},[14,768,137],{},[164,770,771],{},"A page the module adds — unless your application already defines one there.",[773,774,777],"callout",{"title":775,"type":776},"Authentication is client-only","note",[11,778,779,780,782,783,323,785,787,788,790,791,793,794,796],{},"Server rendering produces the signed-out shell: ",[14,781,326],{}," is ",[14,784,563],{},[14,786,677],{},"\nis ",[14,789,714],{},", and ",[14,792,628],{}," throws if you call it on the server. Pages must\nnot assume a user during SSR; the regions bound to ",[14,795,320],{}," fill in on\nhydration.",[798,799,800],"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}",{"title":28,"searchDepth":84,"depth":84,"links":802},[803,804,805,806,807],{"id":50,"depth":67,"text":51},{"id":271,"depth":67,"text":272},{"id":523,"depth":67,"text":524},{"id":580,"depth":67,"text":581},{"id":646,"depth":67,"text":647},"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.","md","guides\u002Fquickstarts\u002Fnuxt",{},30,"\u002Fen\u002Fdocs\u002Fguides\u002Fquickstarts\u002Fnuxt",false,{"title":5,"description":808},"en\u002Fdocs\u002Fguides\u002Fquickstarts\u002Fnuxt","lGXvj-5SnDqJVTctZcYxiTW5-RZPncp7UgCSjNukRGA",[819,824,830,835,840,845,850,855,861,866,871,872,878,883],{"path":820,"title":821,"description":822,"order":445,"key":823},"\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":825,"title":826,"description":827,"order":828,"key":829},"\u002Fen\u002Fdocs\u002Fgetting-started\u002Finstallation","Installing an SDK","The five packages we publish, what each one is for, and the configuration each one takes.",20,"getting-started\u002Finstallation",{"path":831,"title":832,"description":833,"order":445,"key":834},"\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":836,"title":837,"description":838,"order":812,"key":839},"\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.","guides\u002Farchitecture",{"path":841,"title":842,"description":843,"order":828,"key":844},"\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":846,"title":847,"description":848,"order":828,"key":849},"\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":851,"title":852,"description":853,"order":445,"key":854},"\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":856,"title":857,"description":858,"order":859,"key":860},"\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":862,"title":863,"description":864,"order":445,"key":865},"\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":867,"title":868,"description":869,"order":828,"key":870},"\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":813,"title":5,"description":808,"order":812,"key":810},{"path":873,"title":874,"description":875,"order":876,"key":877},"\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":879,"title":880,"description":881,"order":445,"key":882},"\u002Fen\u002Fdocs\u002Freference\u002Fcode-highlighting","Code highlighting","One fence per preloaded Shiki grammar — a fixture, not a reference page.","reference\u002Fcode-highlighting",{"path":884,"title":885,"description":886,"order":812,"key":887},"\u002Fen\u002Fdocs\u002Freference","Reference","Reference material for the HTTP APIs and the SDK packages.","reference\u002Findex",1790698197665]