The CWA is in heavy development
The CWA is still in alpha and not ready for production - some code and implementations are likely to change. If you would like to try out the CWA, please enjoy what we have provided and feel free to provide feedback, or get involved on GitHub.
DraftCwa Layer

Auth Pages

The auth pages the CWA layer provides automatically, their composables, and how to override each one with your own design.

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

RouteComponentComposable
/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>
Any page of your own that isn't CWA-managed content must set 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 layoutcwa-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.