nuxt-x-auth-stack
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:
useXAuthbuilds theStackClientApplazily 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().isConfiguredreports 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 buttonsXAuthSignup— Registration form with email, password, and optional nameXAuthForgotPassword— Password reset request formXAuthMagicLink— Magic link email input formXAuthMagicLinkCallback— Magic link callback handler componentXAuthOtp— OTP (email code) input and verification formXAuthOAuthButton— Single OAuth provider buttonXAuthOAuthButtonGroup— Multiple OAuth provider buttons fromapp.config.tsXAuthHandler— 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 fromapp/, so thexAuthdefaults (redirects, features,legal,uiincl. the video background) now actually apply — previously they were silently ignored. If you relied on the absence of those defaults, re-check yourxAuthoverrides. - Site-overridable
xAuth.ui.*fields (brandName,tagline,background.imageUrl,split.*,form.icon) changed from""toundefinedso consumer overrides win. useXAuth()no longer throws without Stack credentials: it exposesisConfigured, methods degrade gracefully, andgetClient()returnsStackClientApp | null— handlenullif 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 type | Routes | Authenticated | Unauthenticated |
|---|---|---|---|
| Guest-only | /auth/login, /auth/signup, /auth/forgot-password, /auth/magic-link, /auth/otp, /auth/reset-password | Redirect to afterLogin | Allow |
| Public | /auth/handler/*, /auth/logout, /terms, /privacy, plus anything in xAuth.publicRoutes | Allow | Allow |
| All other routes | Everything else | Allow | Redirect 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
| Path | Purpose |
|---|---|
nuxt.config.ts | Registers @nuxt/ui, @nuxt/fonts; sets ssr: false; injects runtime config schema |
app/app.config.ts | All configurable options under xAuth namespace (lives in app/ — Nuxt 4 ignores a root-level app.config.ts) |
app/composables/useXAuth.ts | Stack 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.vue | Auth layout (centered card) |
app/middleware/auth.global.ts | Global route guard |
app/plugins/auth-init.client.ts | Client-side Stack Auth initialization plugin |
Environment Variables
| Variable | Required | Description |
|---|---|---|
NUXT_PUBLIC_STACK_PROJECT_ID | Yes (for auth) | Stack Auth project ID (UUID format). Missing/malformed → graceful degradation, not a crash. |
NUXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY | Yes (for auth) | Stack Auth publishable client key |
