Skip to content

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:

OptionVariable
issuerNUXT_PUBLIC_KLEORA_ISSUER
clientIdNUXT_PUBLIC_KLEORA_CLIENT_ID
redirectUriNUXT_PUBLIC_KLEORA_REDIRECT_URI
scopeNUXT_PUBLIC_KLEORA_SCOPE
audienceNUXT_PUBLIC_KLEORA_AUDIENCE
tenantNUXT_PUBLIC_KLEORA_TENANT
storageNUXT_PUBLIC_KLEORA_STORAGE
apiBaseNUXT_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

ExportWhat 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-authGlobal, keyed on definePageMeta({ auth: true }).
/auth/callbackA page the module adds — unless your application already defines one there.