Auth Pages Tech Spec Vue 3 TypeScript Pinia Vite

Technical Specification — Login & Sign‑up (Vue)

0) Context & Goal

Success depends on high-coverage unit tests and adherence to clean-code practices.

We are building a marketplace (similar to Sharetribe) where businesses list services and customers book them. This document specifies only the Login and Sign‑up pages for the Vue 3 SPA, with emphasis on security, validation, and developer‑friendly structure.

1) Tech Stack & Standards

2) Routes & Navigation

3) UI/UX Requirements

4) Forms & Validation

4.1 Login

4.2 Sign‑up

User types: customer, business (controls conditional fields).

5) API Contracts (proposed)

Base URL: ${VITE_API_BASE}/api/v1

5.1 POST /auth/login

{
  "email": "user@example.com",
  "password": "••••••••",
  "remember": true,
  "captchaToken": "optional"
}
// 200
{
  "user": {"id":"u_123","role":"customer","email":"user@example.com","firstName":"Ada","lastName":"Lovelace","emailVerified": true},
  "tokens": {"access":"jwtAccessToken","refresh":"jwtRefreshToken"}
}
// 400/401
{"error":"INVALID_CREDENTIALS","message":"Email or password is incorrect"}

5.2 POST /auth/register

{
  "role": "customer",
  "firstName": "Ada",
  "lastName": "Lovelace",
  "email": "ada@example.com",
  "password": "S3cure!Pass",
  "business": {"businessName": "Ada Services", "website": "https://ada.services", "phone": "+12125551212"},
  "acceptTos": true,
  "marketingOptIn": false,
  "captchaToken": "optional"
}
// 201
{
  "user": {"id":"u_124","role":"customer","email":"ada@example.com","firstName":"Ada","lastName":"Lovelace","emailVerified": false},
  "tokens": {"access":"jwtAccessToken","refresh":"jwtRefreshToken"},
  "requiresEmailVerification": true
}
// Errors: EMAIL_IN_USE, WEAK_PASSWORD, VALIDATION_ERROR, CAPTCHA_REQUIRED

5.3 POST /auth/refresh

{ "refresh": "jwtRefreshToken" } → { "access": "newAccessToken" }

5.4 POST /auth/logout

{} → 204 No Content

5.5 POST /auth/forgot-password

{ "email": "user@example.com" } → 204 No Content (no enumeration)

5.6 POST /auth/reset-password

{ "token": "resetToken", "password": "NewPass!23" } → 204 No Content

Note: Prefer httpOnly cookies for refresh; JSON mode supported via flag.

6) Security & Privacy (Frontend)

7) State & Interceptors

8) Component Structure

/src
  /features/auth
    components/
      AuthCard.vue
      EmailInput.vue
      PasswordInput.vue
      RoleSelector.vue
      TosCheckbox.vue
    pages/
      LoginPage.vue
      SignupPage.vue
    stores/
      auth.store.ts
    schemas/
      login.schema.ts
      signup.schema.ts
    services/
      auth.api.ts

9) Visual / Interaction Details

10) Analytics & Telemetry

11) i18n

12) Accessibility Checklist

13) Error Handling Matrix

ScenarioUX Behavior
Wrong email/passwordGeneric banner: “Email or password is incorrect.”
Network downInline message with retry; keep inputs.
429 throttlingDisable submit for Retry‑After seconds; show countdown.
Email in useField error on email + link to login.
Weak passwordField error + strength guidance.
Server 5xxToast + show request ID if available.

14) Testing

Unit (Vitest)

E2E (Playwright)

15) DevOps & Config

16) Acceptance Criteria (Definition of Done)

17) Out of Scope (now)

18) Example Type Definitions (TypeScript)

// schemas/login.schema.ts
import { z } from 'zod';
export const LoginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(1),
  remember: z.boolean().optional().default(false)
});
export type LoginInput = z.infer<typeof LoginSchema>;

// schemas/signup.schema.ts
export const SignupSchema = z.object({
  role: z.enum(['customer', 'business']).default('customer'),
  firstName: z.string().min(1).max(80),
  lastName: z.string().min(1).max(80),
  email: z.string().email(),
  password: z.string().min(8),
  confirmPassword: z.string().min(8),
  acceptTos: z.literal(true),
  marketingOptIn: z.boolean().optional(),
  business: z.object({
      businessName: z.string().min(2).max(120),
      website: z.string().url().optional(),
      phone: z.string().optional()
    }).optional()
}).refine((d) => d.password === d.confirmPassword, {
  message: 'Passwords must match',
  path: ['confirmPassword']
});
export type SignupInput = z.infer<typeof SignupSchema>;

19) Skeleton Components (Vue)

<!-- pages/LoginPage.vue -->
<template>
  <AuthCard title="Sign in">
    <VForm @submit="onSubmit" :validation-schema="schema">
      <EmailInput name="email" />
      <PasswordInput name="password" />
      <div class="flex items-center justify-between">
        <VCheckbox name="remember">Remember me</VCheckbox>
        <RouterLink to="/forgot">Forgot password?</RouterLink>
      </div>
      <VButton :loading="pending" type="submit" class="w-full">Sign in</VButton>
      <p class="mt-4 text-sm">No account? <RouterLink to="/signup">Create one</RouterLink></p>
    </VForm>
  </AuthCard>
</template>
<script setup lang="ts">
import { LoginSchema } from '@/features/auth/schemas/login.schema';
import { useAuthStore } from '@/features/auth/stores/auth.store';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter();
const route = useRoute();
const schema = LoginSchema;
const auth = useAuthStore();
const pending = ref(false);
async function onSubmit(values: any) {
  pending.value = true;
  const ok = await auth.login(values);
  pending.value = false;
  if (ok) router.replace((route.query.next as string) || '/dashboard');
}
</script>

20) Open Questions

  1. Token transport: httpOnly cookie vs JSON refresh?
  2. CAPTCHA provider & thresholds?
  3. Analytics provider choice + consent policy?
  4. Email verification required before dashboard?
  5. Default post‑login route: global vs role‑specific?