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.
@kleora-io/nuxt is the browser SDK as a Nuxt 4 module: it owns the client,
adds the callback page, registers a route middleware and gives you an
authenticated $fetch.
npm install @kleora-io/nuxt
1. Register the module
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@kleora-io/nuxt'],
kleora: { issuer: '<issuer>', clientId: '<client_id>' },
})
issuer and clientId are the only required options — a blank one throws as
the application boots, naming both the option and the environment variable that
would have set it. There is no redirectUri here because the module defaults it to its own
callback page on the origin the site is served from: /auth/callback. That
is still the URI you register on the client, exactly as written.
Eight options can come from the environment instead of the config file, which is how one build serves sandbox and production:
| Option | Variable |
|---|---|
issuer | NUXT_PUBLIC_KLEORA_ISSUER |
clientId | NUXT_PUBLIC_KLEORA_CLIENT_ID |
redirectUri | NUXT_PUBLIC_KLEORA_REDIRECT_URI |
scope | NUXT_PUBLIC_KLEORA_SCOPE |
audience | NUXT_PUBLIC_KLEORA_AUDIENCE |
tenant | NUXT_PUBLIC_KLEORA_TENANT |
storage | NUXT_PUBLIC_KLEORA_STORAGE |
apiBase | NUXT_PUBLIC_API_BASE |
The ninth, callbackPath, is build-time only: the module mounts a route there
while building, so an environment variable would move where the browser lands
without moving the page that handles it. Set it in nuxt.config, and keep it
equal to redirectUri's path.
2. Sign in from a page
<!-- app/pages/index.vue -->
<script setup lang="ts">
const { isAuthenticated, isLoading, login, logout } = useKleora()
const user = useKleoraUser()
</script>
<template>
<p v-if="isLoading">Loading…</p>
<button v-else-if="!isAuthenticated" @click="login()">Sign in</button>
<div v-else>
<p>{{ user?.sub }} · {{ user?.email }} · {{ user?.tenant }}</p>
<button @click="logout({ returnTo: '/' })">Sign out</button>
</div>
</template>
logout() with no argument sends the browser to the site root, so / on your
origin has to be one of the client's registered post-logout redirect URIs.
3. Protect a page
The module registers a global middleware named kleora-auth, and it acts only
on pages that ask for it:
<script setup lang="ts">
definePageMeta({ auth: true })
</script>
The protection lives in the page's own meta, so a page cannot lose it by being left out of a list somewhere else. The middleware is client-side: server rendering produces the unauthenticated shell and never redirects.
4. Call an API with the token attached
const api = useKleoraApi()
const invoices = await api('/invoices')
$kleoraApi is a $fetch instance with apiBase as its base URL and a
bearer token on every request. A 401 is retried exactly once with a forced
refresh — so a token that was rejected is never simply sent again — and a
failure arrives as an ApiError carrying the RFC 9457 problem document, not as
a bare status code.
What the module adds for you
| Export | What it is |
|---|---|
useKleora() | The context: client, user, isAuthenticated, isLoading, hasLikelySession, login, logout, getAccessToken, restore, settle. |
useKleoraUser() | A Ref holding the signed-in user, or null. |
useKleoraApi() | The authenticated $fetch instance, as a composable. |
decideSessionRender() | Maps { hasMarker, isLoading, isAuthenticated } onto 'shell' or 'redirect', for a layout that would otherwise flash. |
middleware kleora-auth | Global, keyed on definePageMeta({ auth: true }). |
/auth/callback | A page the module adds — unless your application already defines one there. |