X Enterprises

nuxt-x-auth-stack

Nuxt 4 authentication layer powered by Stack Auth — 10 XAuth-prefixed components, a useXAuth composable with OAuth/magic-link/OTP/password-reset support, global auth middleware, and 7 pre-built pages built on Nuxt UI v4.

nuxt-x-auth-stack

Stack Auth (stack-auth.com) frontend layer for Nuxt 4. Provides 10 auto-imported XAuth-prefixed components, a useXAuth composable, a global auth middleware, and 7 pre-built auth pages — all built on Nuxt UI v4. Client-side only (ssr: false).

Graceful degradation without credentials. Stack credentials are required for auth to function, but the app no longer breaks without them: useXAuth builds the StackClientApp lazily and never throws, public pages render normally, protected routes still redirect to login, and auth pages show a configuration-warning card instead of a form. useXAuth().isConfigured reports whether both variables are set. (Previously the client was constructed eagerly and every page 500'd without valid-format credentials.)

What the Consumer Writes

The layer is batteries-included: auth pages, route protection, and the login UI work out of the box. The minimal consumer setup is three things:

1. Extend the layer in nuxt.config.ts:

export default defineNuxtConfig({
  extends: ['@xenterprises/nuxt-x-auth-stack'],
})

2. Set the Stack Auth credentials in .env:

NUXT_PUBLIC_STACK_PROJECT_ID=your-stack-project-id  # must be UUID format
NUXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=your-publishable-client-key

Both are required for auth to work — without them the app still renders, but auth pages show a configuration warning and auth methods no-op with error toasts (see the note above). The layer maps them to runtimeConfig.public.stack.{projectId,publishableClientKey} automatically; no manual runtimeConfig wiring is needed.

3. (Optional) Override config in app/app.config.ts — it must live in the app/ directory, not the project root:

export default defineAppConfig({
  xAuth: {
    redirects: { afterLogin: '/dashboard' },
    features: { signup: true, oauth: true },
    oauthProviders: [
      { id: 'google', label: 'Google', icon: 'i-simple-icons-google' },
    ],
  },
})

That's it — /auth/login and the other auth pages, the global route guard, and the XAuth* components are available immediately. Everything is opt-out or overridable via the xAuth config namespace (see Configuration).

Installation

npm install @xenterprises/nuxt-x-auth-stack

Peer dependencies: nuxt ^4.0.0 and @nuxt/ui ^4.0.0. @stackframe/js, @nuxt/fonts, and zod are bundled as runtime dependencies of the layer.

What the Layer Provides

Components (auto-imported, XAuth prefix):

  • XAuthLogin — Email/password login form with optional magic link and OAuth buttons
  • XAuthSignup — Registration form with email, password, and optional name
  • XAuthForgotPassword — Password reset request form
  • XAuthMagicLink — Magic link email input form
  • XAuthMagicLinkCallback — Magic link callback handler component
  • XAuthOtp — OTP (email code) input and verification form
  • XAuthOAuthButton — Single OAuth provider button
  • XAuthOAuthButtonGroup — Multiple OAuth provider buttons from app.config.ts
  • XAuthHandler — Handler component for all auth callbacks (OAuth, magic link, email verification, password reset)
  • XAuthForm — Base form wrapper used by auth page components

Composable:

  • useXAuth() — Stack Auth client wrapper with SSR-safe shared state and all auth methods

Pages:

  • /auth/login — Login page
  • /auth/signup — Signup page
  • /auth/forgot-password — Forgot password page
  • /auth/magic-link — Magic link request page
  • /auth/otp — OTP verification page
  • /auth/handler/[...slug] — Catch-all callback handler (OAuth, magic link, email verification, password reset)
  • /auth/logout — Logout action page

Middleware:

  • auth.global — Global route guard; redirects unauthenticated users to login and authenticated users away from guest-only routes

App Config Options

// app.config.ts
export default defineAppConfig({
  xAuth: {
    redirects: {
      login: '/auth/login',
      signup: '/auth/signup',
      afterLogin: '/',
      afterSignup: '/',
      afterLogout: '/auth/login',
      forgotPassword: '/auth/forgot-password',
    },
    features: {
      oauth: false,
      magicLink: false,
      otp: false,
      forgotPassword: true,
      signup: false,
    },
    oauthProviders: [],
  },
})

Note the shipped default for features.signup is false — enable it (and oauth, magicLink, otp) explicitly in the consumer's app/app.config.ts when you want those flows.

Migration notes (latest minor)

  • The layer's own app config moved from the package root to app/app.config.ts. Nuxt 4 only loads a layer's app config from app/, so the xAuth defaults (redirects, features, legal, ui incl. the video background) now actually apply — previously they were silently ignored. If you relied on the absence of those defaults, re-check your xAuth overrides.
  • Site-overridable xAuth.ui.* fields (brandName, tagline, background.imageUrl, split.*, form.icon) changed from "" to undefined so consumer overrides win.
  • useXAuth() no longer throws without Stack credentials: it exposes isConfigured, methods degrade gracefully, and getClient() returns StackClientApp | null — handle null if you use the raw client.

useXAuth() Composable

SSR-safe via Nuxt useState — shared state across all composable instances on the same page.

const {
  user,              // Ref<XAuthUser | null>
  isAuthenticated,   // ComputedRef<boolean>
  isConfigured,      // ComputedRef<boolean> — false when Stack credentials are missing
  isLoading,         // Ref<boolean>
  emailSent,         // Ref<boolean>
  codeSent,          // Ref<boolean>
  config,            // ComputedRef<XAuthConfig>

  login,             // (email, password) => Promise<XAuthUser | null>
  signup,            // (email, password) => Promise<XAuthUser | null>
  logout,            // () => Promise<boolean>
  loginWithProvider, // (providerName) => Promise<boolean>

  forgotPassword,         // (email) => Promise<boolean>
  resetPassword,          // (code, newPassword) => Promise<{ success } | { error }>
  verifyPasswordResetCode,// (code) => Promise<result>

  sendMagicLink,       // (email) => Promise<boolean>
  signInWithMagicLink, // (code) => Promise<{ success } | { error }>

  sendOtp,    // (email) => Promise<string | null>  — returns nonce
  verifyOtp,  // (code, nonce) => Promise<XAuthUser | null>

  verifyEmail, // (code) => Promise<{ success } | { error }>

  getCurrentUser, // () => Promise<XAuthUser | null>
  getToken,       // () => Promise<string | null>
  getAuthHeaders, // () => Promise<{ Authorization: string }>

  resetState, // () => void
  getClient,  // () => StackClientApp | null — null when Stack Auth is not configured
} = useXAuth()

XAuthUser Type

interface XAuthUser {
  id: string
  email: string
  name: string
  avatar?: string
  emailVerified: boolean
  metadata: Record<string, any>
}

Minimal Usage Example

<script setup>
const { user, isAuthenticated, logout } = useXAuth()
</script>

<template>
  <div v-if="isAuthenticated">
    Welcome, {{ user?.name }}
    <UButton @click="logout">Sign Out</UButton>
  </div>
  <XAuthLogin v-else />
</template>

OAuth Providers Example

// app.config.ts
export default defineAppConfig({
  xAuth: {
    features: { oauth: true },
    oauthProviders: [
      { id: 'google', label: 'Google', icon: 'i-simple-icons-google' },
      { id: 'github', label: 'GitHub', icon: 'i-simple-icons-github' },
    ],
  },
})
<XAuthOAuthButtonGroup />

Global Middleware Behavior

Route typeRoutesAuthenticatedUnauthenticated
Guest-only/auth/login, /auth/signup, /auth/forgot-password, /auth/magic-link, /auth/otp, /auth/reset-passwordRedirect to afterLoginAllow
Public/auth/handler/*, /auth/logout, /terms, /privacy, plus anything in xAuth.publicRoutesAllowAllow
All other routesEverything elseAllowRedirect to login

The middleware calls useXAuth() on every route; without credentials getCurrentUser() simply resolves null, so unauthenticated handling (redirect to login) applies and no error is thrown.

Layer Architecture

PathPurpose
nuxt.config.tsRegisters @nuxt/ui, @nuxt/fonts; sets ssr: false; injects runtime config schema
app/app.config.tsAll configurable options under xAuth namespace (lives in app/ — Nuxt 4 ignores a root-level app.config.ts)
app/composables/useXAuth.tsStack Auth client singleton wrapper — all session state and auth methods
app/components/XAuth/10 auto-imported XAuth-prefixed components
app/pages/auth/7 pre-built auth pages
app/layouts/auth.vueAuth layout (centered card)
app/middleware/auth.global.tsGlobal route guard
app/plugins/auth-init.client.tsClient-side Stack Auth initialization plugin

Environment Variables

VariableRequiredDescription
NUXT_PUBLIC_STACK_PROJECT_IDYes (for auth)Stack Auth project ID (UUID format). Missing/malformed → graceful degradation, not a crash.
NUXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEYYes (for auth)Stack Auth publishable client key
Copyright © 2026