Auth Pages
The CWA layer ships complete auth pages for every standard authentication flow. They work immediately after installation. Override any of them by creating the same path in app/pages/.
Provided Pages
| Route | Component | Composable |
|---|---|---|
/login | <CwaAuthLoginPage> | useLogin() |
/forgot-password | <CwaAuthForgotPasswordPage> | useForgotPassword() |
/reset-password/[username]/[token] | <CwaAuthResetPasswordPage> | useResetPassword() |
/verify-email/[username]/[token] | — (auto-verifies on mount) | useVerifyEmail() |
/confirm-new-email/[username]/[newEmail]/[token] | — (auto-confirms on mount) | useVerifyEmail() |
Overriding the Login Page
Create app/pages/login.vue. Use useLogin() to keep the same API wiring:
<!-- app/pages/login.vue -->
<template>
<div class="max-w-sm mx-auto mt-16">
<h1 class="text-2xl font-bold mb-8">Welcome back</h1>
<form @submit.prevent="signIn" class="space-y-4">
<input v-model="credentials.username" type="email" class="input w-full" placeholder="Email" />
<input v-model="credentials.password" type="password" class="input w-full" placeholder="Password" />
<p v-if="error" class="text-red-500 text-sm">{{ error }}</p>
<button type="submit" :disabled="submitting" class="btn-primary w-full">
{{ submitting ? 'Signing in…' : 'Sign in' }}
</button>
</form>
<NuxtLink to="/forgot-password" class="text-sm text-gray-500 mt-4 block">
Forgot your password?
</NuxtLink>
</div>
</template>
<script setup lang="ts">
import { useLogin } from '#imports'
definePageMeta({
cwa: { disabled: true }, // stop CWA resolving this path as a content route
})
const { credentials, signIn, submitting, error } = useLogin()
</script>
cwa: { disabled: true } in definePageMeta — the CWA route middleware is global and will otherwise try to fetch a Route resource for that path. You don't need to set layout — cwa-root-layout is already applied to every page that doesn't declare its own.Composables
useLogin()
const { credentials, signIn, submitting, error } = useLogin()
// credentials.username, credentials.password — reactive form fields
// signIn() — calls cwa.auth.signIn, then navigateTo('/') on success
// submitting — Ref<boolean>
// error — Ref<string | undefined>
useForgotPassword()
const { credentials, doSubmit, submitting, success, error } = useForgotPassword()
// credentials.username — the user's email or username
// doSubmit() — calls cwa.auth.forgotPassword(username)
// success — Ref<boolean> — true after a successful request
useResetPassword()
const { passwords, resetPassword, submitting, success, error, inputErrors } = useResetPassword()
// passwords.first, passwords.second — the new password fields
// username / token are read from the route params internally — not exposed
// resetPassword() — calls cwa.auth.resetPassword({ username, token, passwords })
// inputErrors — per-field validation errors returned by the API on a 422
useVerifyEmail()
Both functions read username / token / newEmail from the route params themselves — you pass nothing:
const { verifyEmail, confirmEmail, submitting, success, error } = useVerifyEmail()
// Verify registration email
onMounted(() => verifyEmail())
// Confirm email address change
onMounted(() => confirmEmail())
useResendVerifyEmail()
const { resendVerifyEmail, submitting, success, error } = useResendVerifyEmail()
// 'current' — resend the registration verification email
resendVerifyEmail(username, 'current')
// 'new' — resend the confirmation for a pending email-address change
resendVerifyEmail(username, 'new')
Protecting Your Own Pages
CWA ships no auth or admin route middleware — its only middleware is the global route resolver. Guard a page from within it, the way the admin panel does:
// app/pages/dashboard.vue
const cwa = useCwa()
onBeforeMount(async () => {
if (!cwa.auth.signedIn.value) await navigateTo('/login')
})
Use !cwa.auth.isAdmin.value for admin-only pages. Auth state is client-side, which is why the check runs on mount rather than during SSR. Write your own route middleware if you prefer that pattern.
useLogin() sends the user to / after a successful sign-in — it doesn't preserve a return path. Capture and restore one yourself if you need it.Registration
There is no built-in /register page — registration flows vary too much between projects. Build your own and use $fetch to POST /users to the API:
await $fetch(`${cwa.apiUrlBase}/users`, {
method: 'POST',
credentials: 'include',
body: { username, emailAddress, plainPassword }
})
The API sends a verification email automatically if verify_on_register: true is set in the bundle config.