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({ redirect }) // redirect is optional
// credentials.username, credentials.password — reactive form fields
// signIn() — calls cwa.auth.signIn, then navigates to the redirect target on success
// submitting — Ref<boolean>
// error — Ref<string | undefined>
The redirect target is the redirect option if you pass one (a string, ref or getter), otherwise the route's ?redirect= query parameter, otherwise /. Only a same-site path is followed; anything else falls back to /. See Where the user goes after signing in.
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
// error — set on a 429 (a reset email was already sent recently), 503 (couldn't send) or 404
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, retryIn } = useResendVerifyEmail()
// retryIn — Ref<number> — seconds until another email can be sent after a 429, counting down to 0
// 'current' — resend the registration verification email
resendVerifyEmail(username, 'current')
// 'new' — resend the confirmation for a pending email-address change
resendVerifyEmail(username, 'new')
See Email Verification for the throttling messages and the CORS header retryIn depends on.
Protecting Your Own Pages
The layer ships two route middleware: cwa-auth for signed-in users and cwa-admin for admins. Opt a page in with definePageMeta:
// app/pages/dashboard.vue
definePageMeta({
middleware: 'cwa-auth',
cwa: { disabled: true }
})
A signed-out visitor is sent to /login?redirect=/dashboard, and the built-in login page returns them there after sign-in. cwa-admin sends a signed-in non-admin to /. See Protecting Pages for details and for writing a middleware with your own rule.
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.