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 /. With the API on a different origin, the server can't see the session, so the redirect to login happens in the browser instead, see When the API is on another origin. 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.