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
| Route | Component | Composable |
|---|---|---|
/login | <CwaLoginPage> | useLogin() |
/forgot-password | <CwaForgotPasswordPage> | useForgotPassword() |
/reset-password/[username]/[token] | <CwaResetPasswordPage> | 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="submit" class="space-y-4">
<input v-model="model.username" type="email" class="input w-full" placeholder="Email" />
<input v-model="model.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="loading" class="btn-primary w-full">
{{ loading ? '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({ layout: 'cwa-root-layout' })
const { model, submit, loading, error } = useLogin()
</script>
Composables
useLogin()
const { model, submit, loading, error } = useLogin()
// model.username, model.password — reactive form fields
// submit() — calls cwa.auth.signIn and redirects on success
// loading — Ref<boolean>
// error — Ref<string | null>
useForgotPassword()
const { model, submit, loading, success, error } = useForgotPassword()
// model.username — the user's email or username
// submit() — POST /password/reset/request/{username}
// success — Ref<boolean> — true after a successful request
useResetPassword()
const { model, submit, loading, success, error } = useResetPassword()
// model.username, model.token — from route params
// model.password, model.passwordConfirm — the new password fields
// submit() — PUT /password/reset/{username}/{token}
useVerifyEmail()
const { verifyEmail, confirmEmail, loading, success, error } = useVerifyEmail()
// Verify registration email
onMounted(() => verifyEmail(route.params.username, route.params.token))
// Confirm email address change
onMounted(() => confirmEmail(route.params.username, route.params.token, route.params.newEmail))
useResendVerifyEmail()
const { resendVerifyEmail, loading, success, error } = useResendVerifyEmail()
resendVerifyEmail(username)
Protecting Your Own Pages
Use the provided route middleware in any page you want to gate behind authentication:
// Signed-in users only
definePageMeta({ middleware: 'auth' })
// Admin users only (ROLE_ADMIN+)
definePageMeta({ middleware: 'admin' })
Unauthenticated users are redirected to /login. After signing in they are returned to the original page.
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.