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 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.