nuxt-huia-headless

Nuxt 3+ bearer-token authentication module for the Huia.Headless identity API
905
Install
npm install nuxt-huia-headless
Latest Version:1.0.0-alpha.17
Vue:3
License:MIT
Last Updated:Oct 16, 2026
Links: GitHub  ·  npm
Maintainer: ayman-elfaki

nuxt-huia-headless

Nuxt 3+ authentication module for Huia.Headless — a bearer-token identity API (register, login, refresh, passkeys, passwordless SMS, external providers) with no OAuth redirect dance for password/phone sign-in. Same dual-layer session principle as nuxt-huia-oidc: the browser only ever holds a sealed, chunked session cookie; access/refresh tokens live server-side in Nitro Storage and are refreshed transparently.

Install

npm install nuxt-huia-headless
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['nuxt-huia-headless'],
  huiaHeadless: {
    baseUrl: 'https://api.example.com',
    // session.password via NUXT_HUIA_HEADLESS_SESSION_PASSWORD (>= 32 chars)
  },
})

Usage

<!-- app/pages/login.vue — the app owns this page; Huia.Headless has no hosted login UI -->
<script setup lang="ts">
const { login } = useHuia()
const result = await login({ email, password })
</script>
<script setup lang="ts">
const { user, loggedIn, logout } = useHuia()
</script>

Passwordless SMS is a two- or three-step flow. startPhoneLogin returns everything a code-entry screen needs (flowId, maskedPhoneNumber, codeLength, expiresInSeconds, resendAvailableInSeconds); resendPhoneLogin sends a fresh code for the same flow (the backend enforces the cooldown and answers resend_too_soon + retryAfterSeconds when it is too early). verifyPhoneLogin returns the bearer session directly for a returning, complete-profile account, or { requiresProfile: true, flowId } for a first-time number that still needs a name. Failed calls carry the backend problem in error: { error: 'invalid' | 'expired' | 'too_many_attempts' | 'rate_limited' | 'daily_limit' | 'resend_too_soon', retryAfterSeconds?, attemptsRemaining? }.

<script setup lang="ts">
const { startPhoneLogin, resendPhoneLogin, verifyPhoneLogin, completePhoneProfile } = useHuia()

const started = await startPhoneLogin({ phoneNumber: '+12025550123' })
if (!started.ok) return // started.error.error === 'rate_limited', ...

// ... after started.resendAvailableInSeconds:
await resendPhoneLogin({ flowId: started.flowId! })

const result = await verifyPhoneLogin({ flowId: started.flowId!, code })
if (result.requiresProfile) {
  await completePhoneProfile({ flowId: result.flowId, firstName, lastName })
}
</script>

External login (Google/GitHub/Microsoft/generic OIDC) is a real browser redirect through the provider, so it starts as a plain link, not a fetch call — externalLoginHref builds it:

<!-- app/pages/login.vue -->
<a :href="useHuia().externalLoginHref('google', '/dashboard')">Sign in with Google</a>
<!-- app/pages/auth/callback.vue — the page huiaHeadless.externalCallbackPage points at (default /auth/callback) -->
<script setup lang="ts">
const { code, returnTo } = useRoute().query
const { exchangeExternalCode, completeExternalProfile } = useHuia()

const result = await exchangeExternalCode(code as string)
if (!result.requiresProfile) await navigateTo((returnTo as string) ?? '/')
// else render a small first/last name form, then call completeExternalProfile({ code: result.flowId, firstName, lastName })
</script>
// server/api/me.get.ts
export default defineEventHandler(async (event) => {
  const { user } = await requireUserSession(event)   // 401 if anonymous
  return { id: user.sub, email: user.email, roles: user.roles }
})

Protect a page with the built-in auth middleware (definePageMeta({ middleware: 'auth' }), or huiaHeadless.middleware.global = true for every page); an unauthenticated visit redirects to huiaHeadless.loginPage (default /login) rather than an external URL — there is nothing external to redirect to. huiaHeadless.middleware.exclude (glob patterns, **/* supported) opts specific pages out; the module's own auth routes and loginPage are always excluded regardless.

logout() is purely local — it clears the session cookie and the stored token record. Huia.Headless's bearer tokens are the framework's stock (non-revocable) format, so unlike nuxt-huia-oidc there is no upstream session to end and no redirect.

Develop

npm install
npm run dev:prepare
npm test            # Vitest unit + integration (mocked Huia.Headless backend)
npm run dev          # playground on http://localhost:3000

.npmrc pins legacy-peer-deps=true (an npm 10 arborist crash on the nuxt peer graph).

Version History

Pre-releases (6)
Version Released Vue License
1.0.0-alpha.17 ^3.0 MIT
1.0.0-alpha.16 ^3.0 MIT
1.0.0-alpha.10 ^3.0 MIT
1.0.0-alpha.9 ^3.0 MIT
1.0.0-alpha.8 ^3.0 MIT