{"_id":"@dommidev10/nuxt-jwt-auth","name":"@dommidev10/nuxt-jwt-auth","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@dommidev10/nuxt-jwt-auth","version":"1.0.0","description":"Nuxt 3 authentication module with 2FA, password reset, and email verification","type":"module","main":"./dist/module.mjs","types":"./dist/types.d.ts","exports":{".":{"import":"./dist/module.mjs","types":"./dist/types.d.ts"},"./runtime/*":"./dist/runtime/*"},"dependencies":{"@nuxt/kit":"^3.14.0","defu":"^6.1.4","jwt-decode":"^4.0.0"},"devDependencies":{"@nuxt/eslint-config":"^1.12.1","@nuxt/schema":"^3.14.0","@nuxt/test-utils":"^3.23.0","@vitest/coverage-v8":"^4.0.17","@vue/test-utils":"^2.4.6","eslint":"^9.39.2","happy-dom":"^20.3.4","nuxt":"^3.14.0","typescript":"^5.7.0","unbuild":"^2.0.0","vitest":"^4.0.17","vue":"^3.5.0"},"peerDependencies":{"nuxt":"^3.0.0"},"repository":{"type":"git","url":"git+https://github.com/dommi10/nuxt-jwt-auth.git"},"keywords":["nuxt","nuxt3","authentication","jwt","2fa","two-factor","auth"],"author":"","license":"MIT","scripts":{"build":"unbuild","dev":"unbuild --stub","lint":"eslint .","lint:fix":"eslint . --fix","typecheck":"tsc --noEmit","test":"vitest","test:run":"vitest run","test:coverage":"vitest run --coverage","playground:dev":"cd playground && pnpm dev","playground:build":"cd playground && pnpm build"},"_id":"@dommidev10/nuxt-jwt-auth@1.0.0","bugs":{"url":"https://github.com/dommi10/nuxt-jwt-auth/issues"},"homepage":"https://github.com/dommi10/nuxt-jwt-auth#readme","_integrity":"sha512-u4hVf7BpNwCp2otVjdauoWPa8Lxk7Y93oYU12+bmMr0sE4iE+12404kIDVASfXzMYb1eju+PU2RzC85qVwW9Bg==","_resolved":"/tmp/c8427a6e02909988d63d808f8162bdc5/dommidev10-nuxt-jwt-auth-1.0.0.tgz","_from":"file:dommidev10-nuxt-jwt-auth-1.0.0.tgz","_nodeVersion":"20.19.6","_npmVersion":"10.8.2","dist":{"integrity":"sha512-u4hVf7BpNwCp2otVjdauoWPa8Lxk7Y93oYU12+bmMr0sE4iE+12404kIDVASfXzMYb1eju+PU2RzC85qVwW9Bg==","shasum":"5488dd75f6cda36c1873abde5579affe9586e1ca","tarball":"https://registry.npmjs.org/@dommidev10/nuxt-jwt-auth/-/nuxt-jwt-auth-1.0.0.tgz","fileCount":22,"unpackedSize":211040,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIDMX8Mobt4PauCehgNv1/tJI3mVSQhbbH0DsNs+EVJgFAiANes1nEXn56/a9rPsKjmeY4/6o5KHKDrEEI/Fyyl+3+Q=="}]},"_npmUser":{"name":"dommidev10","email":"domsbuhendwa2@gmail.com"},"directories":{},"maintainers":[{"name":"dommidev10","email":"domsbuhendwa2@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nuxt-jwt-auth_1.0.0_1768903596166_0.4451776063551094"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-20T10:06:36.086Z","1.0.0":"2026-01-20T10:06:36.342Z","modified":"2026-01-20T10:06:36.598Z"},"maintainers":[{"name":"dommidev10","email":"domsbuhendwa2@gmail.com"}],"description":"Nuxt 3 authentication module with 2FA, password reset, and email verification","homepage":"https://github.com/dommi10/nuxt-jwt-auth#readme","keywords":["nuxt","nuxt3","authentication","jwt","2fa","two-factor","auth"],"repository":{"type":"git","url":"git+https://github.com/dommi10/nuxt-jwt-auth.git"},"bugs":{"url":"https://github.com/dommi10/nuxt-jwt-auth/issues"},"license":"MIT","readme":"# @dommidev10/nuxt-jwt-auth\n\nA fully configurable Nuxt 3 authentication module with JWT support, refresh token rotation, two-factor authentication, password reset, and email verification.\n\n## Why Use This Module?\n\n### The Problem\n\nImplementing authentication in Nuxt 3 applications typically requires:\n\n1. **Repetitive boilerplate** - Token management, cookie handling, API calls\n2. **SSR complexity** - Managing tokens across server and client contexts\n3. **Security concerns** - Proper token refresh, cookie security attributes\n4. **Multiple flows** - Login, logout, 2FA, password reset, email verification\n5. **API flexibility** - Every backend has different response structures\n\n### The Solution\n\n`@dommidev10/nuxt-jwt-auth` provides a **transport-agnostic** authentication layer that:\n\n| Challenge | Solution |\n|-----------|----------|\n| Different API field names | **Body mapping** - Map `email` → `username`, `password` → `pwd` |\n| Different response structures | **JSON pointers** - Extract tokens from any response path |\n| Token refresh complexity | **Automatic refresh** - Schedules refresh before expiration |\n| SSR hydration issues | **Built-in SSR support** - Cookies work seamlessly |\n| Multiple auth flows | **Modular composables** - Enable only what you need |\n\n### Who Should Use This?\n\n✅ **Use this module if you:**\n- Have an existing backend with JWT authentication\n- Need SSR-compatible authentication\n- Want configurable 2FA, password reset, or email verification\n- Don't want to write token management boilerplate\n- Need to adapt to various API response formats\n\n❌ **Consider alternatives if you:**\n- Use OAuth/Social login only (use `@sidebase/nuxt-auth`)\n- Have a Supabase/Firebase backend (use their official SDKs)\n- Need session-based authentication (not JWT)\n\n### Real-World Example: Before vs After\n\n#### ❌ Without This Module (150+ lines)\n\n```typescript\n// composables/useAuth.ts - Manual implementation\nexport function useAuth() {\n  const token = useCookie('auth.token');\n  const refreshToken = useCookie('auth.refresh_token');\n  const session = useState<User | null>('auth:session', () => null);\n  const loading = useState('auth:loading', () => false);\n  const error = useState<string | null>('auth:error', () => null);\n\n  async function signIn(email: string, password: string) {\n    loading.value = true;\n    error.value = null;\n\n    try {\n      const response = await $fetch('/api/auth/login', {\n        method: 'POST',\n        body: { email, password },\n      });\n\n      // Handle 2FA case\n      if (response.requiresTwoFactor) {\n        return { requiresTwoFactor: true, userId: response.userId };\n      }\n\n      // Extract tokens (what if nested? what if different names?)\n      token.value = response.accessToken;\n      refreshToken.value = response.refreshToken;\n\n      // Fetch session\n      await getSession();\n\n      // Schedule token refresh\n      scheduleRefresh();\n\n      return { requiresTwoFactor: false };\n    } catch (err) {\n      error.value = err.message;\n      throw err;\n    } finally {\n      loading.value = false;\n    }\n  }\n\n  async function getSession() { /* ... */ }\n  async function refresh() { /* ... */ }\n  function scheduleRefresh() { /* ... */ }\n  async function signOut() { /* ... */ }\n\n  // ... 100+ more lines for refresh logic, middleware, etc.\n\n  return { token, session, signIn, signOut, /* ... */ };\n}\n```\n\n#### ✅ With This Module (10 lines config)\n\n```typescript\n// nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: ['@dommidev10/nuxt-jwt-auth'],\n\n  auth: {\n    baseURL: 'https://api.example.com',\n    endpoints: {\n      signIn: { path: '/auth/login', method: 'post' },\n      signOut: { path: '/auth/logout', method: 'post' },\n      getSession: { path: '/auth/me', method: 'get' },\n    },\n    token: { signInResponseTokenPointer: '/accessToken' },\n  },\n});\n```\n\n```vue\n<!-- pages/login.vue - That's it! -->\n<script setup>\nconst { signIn, isLoading, error } = useAuth(); // Auto-imported!\n\nasync function handleLogin() {\n  const result = await signIn({ email, password });\n  if (!result.requiresTwoFactor) navigateTo('/dashboard');\n}\n</script>\n```\n\n### Key Benefits\n\n#### 1. Zero Lock-in\n\nThe module is a **transport layer only**. It doesn't:\n- Dictate your backend structure\n- Require specific database schemas\n- Handle email sending (your backend does)\n- Manage user storage\n\n#### 2. Incremental Adoption\n\nEnable features as needed:\n\n```typescript\nauth: {\n  // Start simple\n  twoFactor: { enabled: false },\n  passwordReset: { enabled: false },\n  emailVerification: { enabled: false },\n\n  // Enable later when ready\n  twoFactor: { enabled: true },\n}\n```\n\n#### 3. Works with Any Backend\n\nWhether your API returns:\n\n```json\n// Laravel Sanctum style\n{ \"token\": \"...\", \"user\": { \"id\": 1 } }\n\n// Express/NestJS style\n{ \"accessToken\": \"...\", \"refreshToken\": \"...\" }\n\n// Nested response\n{ \"data\": { \"auth\": { \"jwt\": \"...\" } } }\n```\n\nJust configure the JSON pointers:\n\n```typescript\ntoken: {\n  signInResponseTokenPointer: '/data/auth/jwt', // Works!\n}\n```\n\n#### 4. Type-Safe\n\nFull TypeScript support with autocompletion:\n\n```typescript\nconst { session } = useAuth();\nsession.value?.email  // ✓ Typed\nsession.value?.role   // ✓ Typed (with type augmentation)\n```\n\n---\n\n## Features\n\n- **JWT Authentication** - Complete token-based authentication with cookie storage\n- **Refresh Token Rotation** - Automatic token refresh before expiration\n- **Two-Factor Authentication (2FA)** - Configurable 2FA flow with code verification\n- **Password Reset** - Complete forgot/reset password flow\n- **Email Verification** - Email verification with resend capability\n- **Route Protection** - Middleware for protecting routes (global or per-page)\n- **SSR Compatible** - Works seamlessly with Nuxt's server-side rendering\n- **Fully Configurable** - JSON pointers for API response extraction, body mapping for field names\n- **TypeScript Support** - Full type definitions included\n\n## Table of Contents\n\n- [Why Use This Module?](#why-use-this-module)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Configuration](#configuration)\n  - [Base Configuration](#base-configuration)\n  - [Endpoints](#endpoints)\n  - [Token Configuration](#token-configuration)\n  - [Refresh Token](#refresh-token)\n  - [Two-Factor Authentication](#two-factor-authentication-configuration)\n  - [Password Reset](#password-reset-configuration)\n  - [Email Verification](#email-verification-configuration)\n  - [Session](#session-configuration)\n  - [Pages & Middleware](#pages--middleware-configuration)\n- [Composables](#composables)\n  - [useAuth](#useauth)\n  - [use2FA](#use2fa)\n  - [usePasswordReset](#usepasswordreset)\n  - [useEmailVerification](#useemailverification)\n- [Route Protection](#route-protection)\n- [Complete Examples](#complete-examples)\n  - [Login Page](#login-page)\n  - [2FA Verification Page](#2fa-verification-page)\n  - [Forgot Password Page](#forgot-password-page)\n  - [Reset Password Page](#reset-password-page)\n  - [Email Verification Page](#email-verification-page)\n  - [Protected Dashboard](#protected-dashboard)\n- [Practical Recipes](#practical-recipes)\n  - [Custom API Headers](#custom-api-headers)\n  - [Handle Token Expiration](#handle-token-expiration)\n  - [Persist User Preference](#persist-user-preference)\n  - [Multi-tenant Authentication](#multi-tenant-authentication)\n- [JSON Pointers](#json-pointers)\n- [Body Mapping](#body-mapping)\n- [Backend Integration](#backend-integration)\n- [TypeScript](#typescript)\n- [Troubleshooting](#troubleshooting)\n- [Development](#development)\n- [License](#license)\n\n## Installation\n\n```bash\n# Using pnpm (recommended)\npnpm add @dommidev10/nuxt-jwt-auth\n\n# Using npm\nnpm install @dommidev10/nuxt-jwt-auth\n\n# Using yarn\nyarn add @dommidev10/nuxt-jwt-auth\n```\n\n## Quick Start\n\n### 1. Add the module to your Nuxt config\n\n```typescript\n// nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: ['@dommidev10/nuxt-jwt-auth'],\n\n  auth: {\n    baseURL: 'https://api.example.com',\n    endpoints: {\n      signIn: { path: '/auth/login', method: 'post' },\n      signOut: { path: '/auth/logout', method: 'post' },\n      getSession: { path: '/auth/me', method: 'get' },\n    },\n    token: {\n      signInResponseTokenPointer: '/accessToken',\n    },\n    pages: {\n      login: '/auth/login',\n      home: '/dashboard',\n    },\n  },\n});\n```\n\n### 2. Use the composable in your components\n\n```vue\n<script setup lang=\"ts\">\nconst { signIn, signOut, session, isAuthenticated, isLoading, error } = useAuth();\n\nconst email = ref('');\nconst password = ref('');\n\nasync function handleLogin() {\n  try {\n    const result = await signIn({ email: email.value, password: password.value });\n\n    if (!result.requiresTwoFactor) {\n      navigateTo('/dashboard');\n    }\n  } catch (err) {\n    // Error is automatically set in the error ref\n  }\n}\n</script>\n\n<template>\n  <div>\n    <div v-if=\"isAuthenticated\">\n      <p>Welcome, {{ session?.email }}</p>\n      <button @click=\"signOut\">Logout</button>\n    </div>\n\n    <form v-else @submit.prevent=\"handleLogin\">\n      <input v-model=\"email\" type=\"email\" placeholder=\"Email\" required />\n      <input v-model=\"password\" type=\"password\" placeholder=\"Password\" required />\n      <div v-if=\"error\" class=\"error\">{{ error }}</div>\n      <button :disabled=\"isLoading\">\n        {{ isLoading ? 'Signing in...' : 'Sign In' }}\n      </button>\n    </form>\n  </div>\n</template>\n```\n\n---\n\n## Configuration\n\n### Base Configuration\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `baseURL` | `string` | `''` | Base URL for all API requests |\n| `debug` | `boolean` | `false` | Enable debug logging to console |\n| `refreshOnFocusChanged` | `boolean` | `false` | Refresh session when browser tab regains focus |\n\n```typescript\nauth: {\n  baseURL: 'https://api.example.com',\n  debug: process.env.NODE_ENV === 'development',\n  refreshOnFocusChanged: true,\n}\n```\n\n### Endpoints\n\nConfigure the main authentication endpoints.\n\n```typescript\nauth: {\n  endpoints: {\n    signIn: {\n      path: '/auth/login',\n      method: 'post',\n      body: {\n        email: 'email',       // Maps 'email' → 'email' in request body\n        password: 'password', // Maps 'password' → 'password' in request body\n      },\n    },\n    signOut: {\n      path: '/auth/logout',\n      method: 'post',\n    },\n    getSession: {\n      path: '/auth/me',\n      method: 'get',\n    },\n  },\n}\n```\n\n#### Custom Field Names\n\nIf your API expects different field names:\n\n```typescript\nauth: {\n  endpoints: {\n    signIn: {\n      path: '/auth/login',\n      method: 'post',\n      body: {\n        email: 'username',  // Sends { username: '...' } instead of { email: '...' }\n        password: 'pwd',    // Sends { pwd: '...' } instead of { password: '...' }\n      },\n    },\n  },\n}\n```\n\n### Token Configuration\n\nConfigure how access tokens are handled.\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `signInResponseTokenPointer` | `string` | `'/accessToken'` | JSON pointer to extract token from login response |\n| `cookieName` | `string` | `'auth.token'` | Cookie name for storing the token |\n| `maxAgeInSeconds` | `number` | `3600` | Cookie max age (1 hour) |\n| `sameSiteAttribute` | `'lax' \\| 'strict' \\| 'none'` | `'lax'` | Cookie SameSite attribute |\n| `secureCookieAttribute` | `boolean \\| undefined` | `undefined` | Cookie Secure attribute (auto-detected if undefined) |\n| `httpOnlyCookieAttribute` | `boolean` | `false` | Cookie HttpOnly attribute |\n| `type` | `string` | `'Bearer'` | Token type prefix in Authorization header |\n| `headerName` | `string` | `'Authorization'` | HTTP header name for sending token |\n\n```typescript\nauth: {\n  token: {\n    signInResponseTokenPointer: '/data/accessToken', // For nested responses\n    cookieName: 'auth.token',\n    maxAgeInSeconds: 3600,\n    sameSiteAttribute: 'lax',\n    secureCookieAttribute: true,  // Force secure cookies\n    httpOnlyCookieAttribute: false,\n    type: 'Bearer',\n    headerName: 'Authorization',\n  },\n}\n```\n\n### Refresh Token\n\nEnable automatic token refresh before expiration.\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `enabled` | `boolean` | `false` | Enable refresh token functionality |\n| `refreshOnlyToken` | `boolean` | `true` | Only refresh token (not full session) |\n| `refreshBeforeExpiryInSeconds` | `number` | `300` | Refresh token X seconds before expiry |\n\n```typescript\nauth: {\n  refresh: {\n    enabled: true,\n    endpoint: {\n      path: '/auth/refresh',\n      method: 'post',\n    },\n    token: {\n      signInResponseRefreshTokenPointer: '/refreshToken',\n      refreshResponseTokenPointer: '/accessToken',\n      refreshResponseRefreshTokenPointer: '/refreshToken', // For token rotation\n      refreshRequestTokenPointer: 'refreshToken', // Key in request body\n      cookieName: 'auth.refresh_token',\n      maxAgeInSeconds: 604800, // 7 days\n      sameSiteAttribute: 'lax',\n      secureCookieAttribute: undefined,\n      httpOnlyCookieAttribute: false,\n    },\n    refreshOnlyToken: true,\n    refreshBeforeExpiryInSeconds: 300, // Refresh 5 min before expiry\n  },\n}\n```\n\n**How it works:**\n\n1. When user logs in, both access and refresh tokens are stored\n2. A timer is set to refresh the token 5 minutes before expiry\n3. When the timer fires, the module calls the refresh endpoint\n4. New tokens are stored and a new timer is set\n5. This continues seamlessly as long as the user is active\n\n### Two-Factor Authentication Configuration\n\nEnable 2FA support for your application.\n\n```typescript\nauth: {\n  twoFactor: {\n    enabled: true,\n\n    endpoints: {\n      verify: {\n        path: '/auth/2fa/verify',\n        method: 'post',\n        body: {\n          userId: 'userId',\n          code: 'code',\n        },\n      },\n      resend: {\n        path: '/auth/2fa/resend',\n        method: 'post',\n        body: {\n          userId: 'userId',\n        },\n      },\n      // Optional: Enable/disable 2FA for user account\n      enable: {\n        path: '/auth/2fa/enable',\n        method: 'post',\n        body: {\n          password: 'password',\n        },\n      },\n      disable: {\n        path: '/auth/2fa/disable',\n        method: 'post',\n        body: {\n          password: 'password',\n          code: 'code',\n        },\n      },\n    },\n\n    // Response extraction from login when 2FA is required\n    response: {\n      requiresTwoFactorPointer: '/requiresTwoFactor',\n      userIdPointer: '/userId',\n      emailPointer: '/email',       // Optional: masked email\n      expiresInPointer: '/expiresIn', // Optional: code expiry time\n    },\n\n    // Response extraction from verify endpoint\n    verifyResponse: {\n      tokenPointer: '/accessToken',\n      refreshTokenPointer: '/refreshToken',\n    },\n\n    // Response extraction from resend endpoint\n    resendResponse: {\n      messagePointer: '/message',\n      cooldownPointer: '/cooldownSeconds',\n    },\n\n    // UI Configuration\n    verifyPage: '/auth/verify-2fa',\n    codeExpirationInSeconds: 300,  // 5 minutes\n    resendCooldownInSeconds: 60,   // 1 minute cooldown between resends\n  },\n}\n```\n\n**Expected API Response for Login (when 2FA required):**\n\n```json\n{\n  \"requiresTwoFactor\": true,\n  \"userId\": \"user-123\",\n  \"email\": \"u***@example.com\",\n  \"expiresIn\": 300\n}\n```\n\n### Password Reset Configuration\n\nEnable password reset functionality.\n\n```typescript\nauth: {\n  passwordReset: {\n    enabled: true,\n\n    endpoints: {\n      request: {\n        path: '/auth/forgot-password',\n        method: 'post',\n        body: {\n          email: 'email',\n        },\n      },\n      reset: {\n        path: '/auth/reset-password',\n        method: 'post',\n        body: {\n          token: 'token',\n          password: 'password',\n          passwordConfirm: 'passwordConfirm',\n        },\n      },\n    },\n\n    // Response extraction\n    requestResponse: {\n      messagePointer: '/message',\n    },\n    resetResponse: {\n      messagePointer: '/message',\n      // Optional: Auto-login after password reset\n      tokenPointer: '/accessToken',\n      refreshTokenPointer: '/refreshToken',\n    },\n\n    // UI Configuration\n    requestPage: '/auth/forgot-password',\n    resetPage: '/auth/reset-password',\n  },\n}\n```\n\n### Email Verification Configuration\n\nEnable email verification functionality.\n\n```typescript\nauth: {\n  emailVerification: {\n    enabled: true,\n\n    endpoints: {\n      verify: {\n        path: '/auth/verify-email',\n        method: 'post',\n        body: {\n          token: 'token',\n        },\n      },\n      resend: {\n        path: '/auth/resend-verification',\n        method: 'post',\n        body: {\n          email: 'email',\n        },\n      },\n    },\n\n    // Response extraction\n    verifyResponse: {\n      messagePointer: '/message',\n      verifiedPointer: '/verified',\n    },\n    resendResponse: {\n      messagePointer: '/message',\n    },\n\n    // UI Configuration\n    verifyPage: '/auth/verify-email',\n  },\n}\n```\n\n### Session Configuration\n\nConfigure session data handling.\n\n```typescript\nauth: {\n  session: {\n    // Define expected session data types (for TypeScript)\n    dataType: {\n      id: 'string',\n      email: 'string',\n      firstName: 'string',\n      lastName: 'string',\n      role: 'string',\n      twoFactorEnabled: 'boolean',\n    },\n    // JSON pointer if session data is nested in response\n    dataResponsePointer: '/user', // For { user: { id, email, ... } }\n  },\n}\n```\n\n### Pages & Middleware Configuration\n\nConfigure authentication pages and route protection.\n\n```typescript\nauth: {\n  pages: {\n    login: '/auth/login',   // Redirect here when not authenticated\n    home: '/dashboard',     // Redirect here after login\n  },\n\n  globalMiddleware: true, // Apply auth middleware to all routes\n\n  publicRoutes: [\n    '/',                    // Exact match\n    '/about',\n    '/auth/*',              // Wildcard: all /auth/* routes\n    '^/blog/\\\\d+$',         // Regex: /blog/123 but not /blog/abc\n  ],\n}\n```\n\n---\n\n## Composables\n\n### useAuth\n\nThe main authentication composable. Always available.\n\n```typescript\nconst {\n  // Reactive State\n  token,              // Ref<string | null> - Current access token\n  refreshToken,       // Ref<string | null> - Current refresh token\n  session,            // Ref<Session | null> - User session data\n  isAuthenticated,    // ComputedRef<boolean> - Is user logged in\n  isLoading,          // ComputedRef<boolean> - Is operation in progress\n  error,              // ComputedRef<string | null> - Last error message\n\n  // Actions\n  signIn,             // (credentials) => Promise<SignInResult>\n  signOut,            // () => Promise<void>\n  getSession,         // () => Promise<Session | null>\n  refresh,            // () => Promise<boolean>\n  completeSignIn,     // (accessToken, refreshToken?) => Promise<void>\n  clearAuth,          // () => void\n} = useAuth();\n```\n\n#### signIn(credentials)\n\nAuthenticate user with credentials.\n\n```typescript\ninterface SignInResult {\n  requiresTwoFactor: boolean;\n  userId?: string;\n  email?: string;\n  expiresIn?: number;\n}\n\nconst result = await signIn({\n  email: 'user@example.com',\n  password: 'password123',\n});\n\nif (result.requiresTwoFactor) {\n  // Redirect to 2FA verification\n  navigateTo('/auth/verify-2fa');\n} else {\n  // Login successful\n  navigateTo('/dashboard');\n}\n```\n\n#### signOut()\n\nLog out the current user.\n\n```typescript\nawait signOut();\n// User is logged out, tokens cleared, redirected to login\n```\n\n#### getSession()\n\nFetch the current user's session data.\n\n```typescript\nconst session = await getSession();\nconsole.log(session?.email, session?.role);\n```\n\n#### refresh()\n\nManually refresh the access token.\n\n```typescript\nconst success = await refresh();\nif (!success) {\n  // Refresh failed, user needs to login again\n}\n```\n\n#### completeSignIn(accessToken, refreshToken?)\n\nComplete authentication after 2FA verification. Usually called internally by `use2FA`.\n\n```typescript\nawait completeSignIn('new-access-token', 'new-refresh-token');\n```\n\n#### clearAuth()\n\nClear all authentication state without calling logout endpoint.\n\n```typescript\nclearAuth();\n```\n\n---\n\n### use2FA\n\nTwo-factor authentication composable. Available when `twoFactor.enabled: true`.\n\n```typescript\nconst {\n  // Reactive State\n  pending2FA,        // Readonly<Ref<Pending2FA | null>> - Current 2FA state\n  is2FAExpired,      // ComputedRef<boolean> - Has the code expired\n  timeRemaining,     // ComputedRef<number> - Seconds until expiry\n  canResend,         // ComputedRef<boolean> - Can resend code\n  resendCooldown,    // Readonly<Ref<number>> - Seconds until can resend\n  isLoading,         // ComputedRef<boolean>\n  error,             // ComputedRef<string | null>\n\n  // Actions\n  initiate2FA,       // (userId, email?, expiresIn?) => void\n  verify2FA,         // (code) => Promise<void>\n  resend2FA,         // () => Promise<{ message?, cooldownSeconds? }>\n  cancel2FA,         // () => void\n} = use2FA();\n```\n\n#### Pending2FA Interface\n\n```typescript\ninterface Pending2FA {\n  userId: string;\n  email?: string;\n  expiresAt: number;  // Timestamp when code expires\n}\n```\n\n#### initiate2FA(userId, email?, expiresIn?)\n\nStart the 2FA verification process. Called automatically by `signIn` when 2FA is required.\n\n```typescript\ninitiate2FA('user-123', 'u***@example.com', 300);\n```\n\n#### verify2FA(code)\n\nVerify the 2FA code and complete authentication.\n\n```typescript\ntry {\n  await verify2FA('123456');\n  // User is now fully authenticated\n  navigateTo('/dashboard');\n} catch (err) {\n  // Invalid code, error is set automatically\n}\n```\n\n#### resend2FA()\n\nRequest a new 2FA code.\n\n```typescript\nif (canResend.value) {\n  const { message, cooldownSeconds } = await resend2FA();\n  // New code sent, cooldown timer started\n}\n```\n\n#### cancel2FA()\n\nCancel the 2FA process and clear state.\n\n```typescript\ncancel2FA();\nnavigateTo('/auth/login');\n```\n\n---\n\n### usePasswordReset\n\nPassword reset composable. Available when `passwordReset.enabled: true`.\n\n```typescript\nconst {\n  // Reactive State\n  isLoading,         // Readonly<Ref<boolean>>\n  error,             // Readonly<Ref<string | null>>\n  emailSent,         // Readonly<Ref<boolean>> - Reset email sent successfully\n\n  // Actions\n  requestReset,      // (email) => Promise<{ message? }>\n  resetPassword,     // (token, password, passwordConfirm?) => Promise<{ message? }>\n  clearState,        // () => void\n} = usePasswordReset();\n```\n\n#### requestReset(email)\n\nRequest a password reset email.\n\n```typescript\ntry {\n  const { message } = await requestReset('user@example.com');\n  // emailSent.value is now true\n  console.log(message); // \"Check your email for reset instructions\"\n} catch (err) {\n  // Error handled automatically\n}\n```\n\n#### resetPassword(token, password, passwordConfirm?)\n\nReset the password using the token from the email link.\n\n```typescript\nconst route = useRoute();\nconst token = route.query.token as string;\n\ntry {\n  await resetPassword(token, 'newPassword123', 'newPassword123');\n  // Password reset successful\n  // If auto-login is configured, user is now authenticated\n  navigateTo('/dashboard');\n} catch (err) {\n  // Invalid token or password requirements not met\n}\n```\n\n---\n\n### useEmailVerification\n\nEmail verification composable. Available when `emailVerification.enabled: true`.\n\n```typescript\nconst {\n  // Reactive State\n  isLoading,              // Readonly<Ref<boolean>>\n  error,                  // Readonly<Ref<string | null>>\n  isVerified,             // Readonly<Ref<boolean>>\n\n  // Actions\n  verifyEmail,            // (token) => Promise<{ message?, verified? }>\n  resendVerification,     // (email?) => Promise<{ message? }>\n  clearState,             // () => void\n} = useEmailVerification();\n```\n\n#### verifyEmail(token)\n\nVerify the email using the token from the verification link.\n\n```typescript\nconst route = useRoute();\nconst token = route.query.token as string;\n\ntry {\n  const { message, verified } = await verifyEmail(token);\n  // isVerified.value is now true\n  console.log(message); // \"Email verified successfully\"\n} catch (err) {\n  // Invalid or expired token\n}\n```\n\n#### resendVerification(email?)\n\nResend the verification email.\n\n```typescript\ntry {\n  const { message } = await resendVerification('user@example.com');\n  console.log(message); // \"Verification email sent\"\n} catch (err) {\n  // Error handled automatically\n}\n```\n\n---\n\n## Route Protection\n\nThe module includes middleware for protecting routes.\n\n### Global Middleware\n\nApply to all routes automatically:\n\n```typescript\n// nuxt.config.ts\nauth: {\n  globalMiddleware: true,\n  publicRoutes: [\n    '/',\n    '/auth/*',\n    '/about',\n    '/pricing',\n  ],\n}\n```\n\n### Per-Page Middleware\n\nApply to specific pages:\n\n```typescript\n// nuxt.config.ts\nauth: {\n  globalMiddleware: false, // Disable global\n}\n```\n\n```vue\n<!-- pages/dashboard.vue -->\n<script setup>\ndefinePageMeta({\n  middleware: 'auth',\n});\n</script>\n```\n\n### Public Route Patterns\n\nThe module supports three types of route matching:\n\n| Pattern | Example | Matches |\n|---------|---------|---------|\n| Exact | `/about` | Only `/about` |\n| Wildcard | `/auth/*` | `/auth/login`, `/auth/register`, etc. |\n| Regex | `^/blog/\\d+$` | `/blog/123` but not `/blog/abc` |\n\n```typescript\npublicRoutes: [\n  '/',                     // Exact: only root\n  '/about',                // Exact: only /about\n  '/auth/*',               // Wildcard: all auth routes\n  '/api/*',                // Wildcard: all API routes\n  '^/posts/\\\\d+$',         // Regex: /posts/123\n  '^/users/[a-z]+/profile$', // Regex: /users/john/profile\n],\n```\n\n### Redirect Behavior\n\n| Scenario | Action |\n|----------|--------|\n| Unauthenticated → Protected | Redirect to login with `?redirect=/original-path` |\n| Authenticated → Auth page | Redirect to home |\n| Any → Public route | Allow access |\n\n---\n\n## Complete Examples\n\n### Login Page\n\n```vue\n<!-- pages/auth/login.vue -->\n<script setup lang=\"ts\">\ndefinePageMeta({\n  layout: 'auth',\n});\n\nconst { signIn, isLoading, error } = useAuth();\nconst { initiate2FA } = use2FA();\nconst route = useRoute();\n\nconst email = ref('');\nconst password = ref('');\n\nasync function handleSubmit() {\n  try {\n    const result = await signIn({\n      email: email.value,\n      password: password.value,\n    });\n\n    if (result.requiresTwoFactor) {\n      // Store 2FA state and redirect\n      initiate2FA(result.userId!, result.email, result.expiresIn);\n      navigateTo('/auth/verify-2fa');\n    } else {\n      // Successful login - redirect to original destination or home\n      const redirect = route.query.redirect as string;\n      navigateTo(redirect || '/dashboard');\n    }\n  } catch (err) {\n    // Error is automatically set in error ref\n  }\n}\n</script>\n\n<template>\n  <div class=\"login-page\">\n    <h1>Sign In</h1>\n\n    <form @submit.prevent=\"handleSubmit\">\n      <div class=\"form-group\">\n        <label for=\"email\">Email</label>\n        <input\n          id=\"email\"\n          v-model=\"email\"\n          type=\"email\"\n          placeholder=\"you@example.com\"\n          required\n          autocomplete=\"email\"\n        />\n      </div>\n\n      <div class=\"form-group\">\n        <label for=\"password\">Password</label>\n        <input\n          id=\"password\"\n          v-model=\"password\"\n          type=\"password\"\n          placeholder=\"••••••••\"\n          required\n          autocomplete=\"current-password\"\n        />\n      </div>\n\n      <div v-if=\"error\" class=\"error-message\">\n        {{ error }}\n      </div>\n\n      <button type=\"submit\" :disabled=\"isLoading\">\n        {{ isLoading ? 'Signing in...' : 'Sign In' }}\n      </button>\n\n      <div class=\"links\">\n        <NuxtLink to=\"/auth/forgot-password\">Forgot password?</NuxtLink>\n        <NuxtLink to=\"/auth/register\">Create account</NuxtLink>\n      </div>\n    </form>\n  </div>\n</template>\n```\n\n### 2FA Verification Page\n\n```vue\n<!-- pages/auth/verify-2fa.vue -->\n<script setup lang=\"ts\">\ndefinePageMeta({\n  layout: 'auth',\n});\n\nconst {\n  pending2FA,\n  verify2FA,\n  resend2FA,\n  cancel2FA,\n  is2FAExpired,\n  timeRemaining,\n  canResend,\n  resendCooldown,\n  isLoading,\n  error,\n} = use2FA();\n\nconst code = ref('');\nconst resendMessage = ref('');\n\n// Redirect if no pending 2FA\nonMounted(() => {\n  if (!pending2FA.value) {\n    navigateTo('/auth/login');\n  }\n});\n\n// Format time remaining\nconst formattedTime = computed(() => {\n  const minutes = Math.floor(timeRemaining.value / 60);\n  const seconds = timeRemaining.value % 60;\n  return `${minutes}:${seconds.toString().padStart(2, '0')}`;\n});\n\nasync function handleVerify() {\n  try {\n    await verify2FA(code.value);\n    navigateTo('/dashboard');\n  } catch (err) {\n    code.value = ''; // Clear code on error\n  }\n}\n\nasync function handleResend() {\n  try {\n    const { message } = await resend2FA();\n    resendMessage.value = message || 'Code sent!';\n    setTimeout(() => {\n      resendMessage.value = '';\n    }, 3000);\n  } catch (err) {\n    // Error handled automatically\n  }\n}\n\nfunction handleCancel() {\n  cancel2FA();\n  navigateTo('/auth/login');\n}\n</script>\n\n<template>\n  <div class=\"verify-2fa-page\">\n    <h1>Two-Factor Authentication</h1>\n\n    <div v-if=\"pending2FA\" class=\"content\">\n      <p>\n        Enter the verification code sent to\n        <strong>{{ pending2FA.email || 'your email' }}</strong>\n      </p>\n\n      <div v-if=\"is2FAExpired\" class=\"expired-message\">\n        <p>Your code has expired.</p>\n        <button @click=\"handleResend\" :disabled=\"!canResend || isLoading\">\n          Request New Code\n        </button>\n      </div>\n\n      <form v-else @submit.prevent=\"handleVerify\">\n        <div class=\"form-group\">\n          <label for=\"code\">Verification Code</label>\n          <input\n            id=\"code\"\n            v-model=\"code\"\n            type=\"text\"\n            inputmode=\"numeric\"\n            pattern=\"[0-9]*\"\n            maxlength=\"6\"\n            placeholder=\"000000\"\n            required\n            autocomplete=\"one-time-code\"\n          />\n        </div>\n\n        <p class=\"timer\">Code expires in {{ formattedTime }}</p>\n\n        <div v-if=\"error\" class=\"error-message\">\n          {{ error }}\n        </div>\n\n        <div v-if=\"resendMessage\" class=\"success-message\">\n          {{ resendMessage }}\n        </div>\n\n        <button type=\"submit\" :disabled=\"isLoading || code.length < 6\">\n          {{ isLoading ? 'Verifying...' : 'Verify' }}\n        </button>\n\n        <div class=\"actions\">\n          <button\n            type=\"button\"\n            @click=\"handleResend\"\n            :disabled=\"!canResend || isLoading\"\n            class=\"link-button\"\n          >\n            {{ canResend ? 'Resend code' : `Resend in ${resendCooldown}s` }}\n          </button>\n\n          <button\n            type=\"button\"\n            @click=\"handleCancel\"\n            class=\"link-button\"\n          >\n            Cancel\n          </button>\n        </div>\n      </form>\n    </div>\n\n    <div v-else class=\"no-session\">\n      <p>No verification session found.</p>\n      <NuxtLink to=\"/auth/login\">Return to Login</NuxtLink>\n    </div>\n  </div>\n</template>\n```\n\n### Forgot Password Page\n\n```vue\n<!-- pages/auth/forgot-password.vue -->\n<script setup lang=\"ts\">\ndefinePageMeta({\n  layout: 'auth',\n});\n\nconst { requestReset, emailSent, isLoading, error, clearState } = usePasswordReset();\n\nconst email = ref('');\n\nasync function handleSubmit() {\n  try {\n    await requestReset(email.value);\n    // emailSent.value is now true\n  } catch (err) {\n    // Error handled automatically\n  }\n}\n\n// Clear state when leaving page\nonUnmounted(() => {\n  clearState();\n});\n</script>\n\n<template>\n  <div class=\"forgot-password-page\">\n    <h1>Forgot Password</h1>\n\n    <div v-if=\"emailSent\" class=\"success-state\">\n      <div class=\"icon\">✓</div>\n      <h2>Check your email</h2>\n      <p>\n        We've sent a password reset link to\n        <strong>{{ email }}</strong>\n      </p>\n      <p class=\"note\">\n        Didn't receive the email? Check your spam folder or\n        <button @click=\"emailSent = false\" class=\"link-button\">\n          try again\n        </button>\n      </p>\n      <NuxtLink to=\"/auth/login\" class=\"back-link\">\n        Back to Login\n      </NuxtLink>\n    </div>\n\n    <form v-else @submit.prevent=\"handleSubmit\">\n      <p>Enter your email address and we'll send you a link to reset your password.</p>\n\n      <div class=\"form-group\">\n        <label for=\"email\">Email</label>\n        <input\n          id=\"email\"\n          v-model=\"email\"\n          type=\"email\"\n          placeholder=\"you@example.com\"\n          required\n          autocomplete=\"email\"\n        />\n      </div>\n\n      <div v-if=\"error\" class=\"error-message\">\n        {{ error }}\n      </div>\n\n      <button type=\"submit\" :disabled=\"isLoading\">\n        {{ isLoading ? 'Sending...' : 'Send Reset Link' }}\n      </button>\n\n      <NuxtLink to=\"/auth/login\" class=\"back-link\">\n        Back to Login\n      </NuxtLink>\n    </form>\n  </div>\n</template>\n```\n\n### Reset Password Page\n\n```vue\n<!-- pages/auth/reset-password.vue -->\n<script setup lang=\"ts\">\ndefinePageMeta({\n  layout: 'auth',\n});\n\nconst { resetPassword, isLoading, error, clearState } = usePasswordReset();\nconst route = useRoute();\n\nconst password = ref('');\nconst passwordConfirm = ref('');\nconst success = ref(false);\n\nconst token = computed(() => route.query.token as string);\n\n// Redirect if no token\nonMounted(() => {\n  if (!token.value) {\n    navigateTo('/auth/forgot-password');\n  }\n});\n\nasync function handleSubmit() {\n  if (password.value !== passwordConfirm.value) {\n    return;\n  }\n\n  try {\n    await resetPassword(token.value, password.value, passwordConfirm.value);\n    success.value = true;\n\n    // If auto-login is configured, redirect to dashboard\n    // Otherwise, redirect to login\n    setTimeout(() => {\n      navigateTo('/auth/login?reset=success');\n    }, 2000);\n  } catch (err) {\n    // Error handled automatically\n  }\n}\n\nconst passwordsMatch = computed(() => {\n  return password.value === passwordConfirm.value;\n});\n\nonUnmounted(() => {\n  clearState();\n});\n</script>\n\n<template>\n  <div class=\"reset-password-page\">\n    <h1>Reset Password</h1>\n\n    <div v-if=\"success\" class=\"success-state\">\n      <div class=\"icon\">✓</div>\n      <h2>Password Reset!</h2>\n      <p>Your password has been successfully reset.</p>\n      <p>Redirecting to login...</p>\n    </div>\n\n    <div v-else-if=\"!token\" class=\"no-token\">\n      <p>Invalid or missing reset token.</p>\n      <NuxtLink to=\"/auth/forgot-password\">\n        Request a new reset link\n      </NuxtLink>\n    </div>\n\n    <form v-else @submit.prevent=\"handleSubmit\">\n      <div class=\"form-group\">\n        <label for=\"password\">New Password</label>\n        <input\n          id=\"password\"\n          v-model=\"password\"\n          type=\"password\"\n          placeholder=\"••••••••\"\n          required\n          minlength=\"8\"\n          autocomplete=\"new-password\"\n        />\n      </div>\n\n      <div class=\"form-group\">\n        <label for=\"passwordConfirm\">Confirm Password</label>\n        <input\n          id=\"passwordConfirm\"\n          v-model=\"passwordConfirm\"\n          type=\"password\"\n          placeholder=\"••••••••\"\n          required\n          autocomplete=\"new-password\"\n        />\n        <p v-if=\"passwordConfirm && !passwordsMatch\" class=\"field-error\">\n          Passwords do not match\n        </p>\n      </div>\n\n      <div v-if=\"error\" class=\"error-message\">\n        {{ error }}\n      </div>\n\n      <button type=\"submit\" :disabled=\"isLoading || !passwordsMatch\">\n        {{ isLoading ? 'Resetting...' : 'Reset Password' }}\n      </button>\n    </form>\n  </div>\n</template>\n```\n\n### Email Verification Page\n\n```vue\n<!-- pages/auth/verify-email.vue -->\n<script setup lang=\"ts\">\ndefinePageMeta({\n  layout: 'auth',\n});\n\nconst { verifyEmail, resendVerification, isVerified, isLoading, error, clearState } = useEmailVerification();\nconst { session } = useAuth();\nconst route = useRoute();\n\nconst resendEmail = ref('');\nconst resendSent = ref(false);\n\nconst token = computed(() => route.query.token as string);\n\n// Auto-verify on page load\nonMounted(async () => {\n  if (token.value) {\n    try {\n      await verifyEmail(token.value);\n    } catch (err) {\n      // Error handled automatically\n    }\n  }\n});\n\nasync function handleResend() {\n  const email = resendEmail.value || session.value?.email;\n  if (!email) return;\n\n  try {\n    await resendVerification(email);\n    resendSent.value = true;\n  } catch (err) {\n    // Error handled automatically\n  }\n}\n\nonUnmounted(() => {\n  clearState();\n});\n</script>\n\n<template>\n  <div class=\"verify-email-page\">\n    <h1>Email Verification</h1>\n\n    <div v-if=\"isLoading\" class=\"loading-state\">\n      <div class=\"spinner\"></div>\n      <p>Verifying your email...</p>\n    </div>\n\n    <div v-else-if=\"isVerified\" class=\"success-state\">\n      <div class=\"icon\">✓</div>\n      <h2>Email Verified!</h2>\n      <p>Your email has been successfully verified.</p>\n      <NuxtLink to=\"/dashboard\" class=\"button\">\n        Continue to Dashboard\n      </NuxtLink>\n    </div>\n\n    <div v-else-if=\"error\" class=\"error-state\">\n      <div class=\"icon\">✗</div>\n      <h2>Verification Failed</h2>\n      <p>{{ error }}</p>\n\n      <div v-if=\"resendSent\" class=\"resend-success\">\n        <p>A new verification link has been sent to your email.</p>\n      </div>\n\n      <div v-else class=\"resend-form\">\n        <p>Enter your email to receive a new verification link:</p>\n        <div class=\"form-group\">\n          <input\n            v-model=\"resendEmail\"\n            type=\"email\"\n            placeholder=\"you@example.com\"\n            :value=\"session?.email\"\n          />\n          <button @click=\"handleResend\" :disabled=\"isLoading\">\n            Resend Link\n          </button>\n        </div>\n      </div>\n\n      <NuxtLink to=\"/auth/login\" class=\"back-link\">\n        Back to Login\n      </NuxtLink>\n    </div>\n\n    <div v-else class=\"no-token\">\n      <p>No verification token found.</p>\n      <NuxtLink to=\"/\">Go Home</NuxtLink>\n    </div>\n  </div>\n</template>\n```\n\n### Protected Dashboard\n\n```vue\n<!-- pages/dashboard.vue -->\n<script setup lang=\"ts\">\n// This page is protected by the auth middleware\n\nconst { session, signOut, isLoading } = useAuth();\n\nasync function handleLogout() {\n  await signOut();\n  // User is automatically redirected to login\n}\n</script>\n\n<template>\n  <div class=\"dashboard\">\n    <header>\n      <h1>Dashboard</h1>\n      <div class=\"user-info\">\n        <span>{{ session?.email }}</span>\n        <button @click=\"handleLogout\" :disabled=\"isLoading\">\n          {{ isLoading ? 'Logging out...' : 'Logout' }}\n        </button>\n      </div>\n    </header>\n\n    <main>\n      <div class=\"welcome-card\">\n        <h2>Welcome, {{ session?.firstName || 'User' }}!</h2>\n        <p>You are logged in as {{ session?.role || 'member' }}.</p>\n      </div>\n\n      <div class=\"user-details\">\n        <h3>Your Profile</h3>\n        <dl>\n          <dt>Email</dt>\n          <dd>{{ session?.email }}</dd>\n\n          <dt>User ID</dt>\n          <dd>{{ session?.id }}</dd>\n\n          <dt>2FA Enabled</dt>\n          <dd>{{ session?.twoFactorEnabled ? 'Yes' : 'No' }}</dd>\n        </dl>\n      </div>\n    </main>\n  </div>\n</template>\n```\n\n---\n\n## Practical Recipes\n\n### Custom API Headers\n\nAdd custom headers to all auth requests (e.g., tenant ID, API version):\n\n```typescript\n// composables/useAuthFetch.ts\nexport function useAuthFetch() {\n  const { token } = useAuth();\n  const config = useRuntimeConfig();\n\n  return $fetch.create({\n    baseURL: config.public.auth.baseURL,\n    onRequest({ options }) {\n      // Add auth header\n      if (token.value) {\n        options.headers = {\n          ...options.headers,\n          Authorization: `Bearer ${token.value}`,\n        };\n      }\n\n      // Add custom headers\n      options.headers = {\n        ...options.headers,\n        'X-Tenant-ID': 'my-tenant',\n        'X-API-Version': '2024-01',\n      };\n    },\n  });\n}\n```\n\n### Handle Token Expiration\n\nCreate a global error handler for expired tokens:\n\n```typescript\n// plugins/auth-error-handler.client.ts\nexport default defineNuxtPlugin(() => {\n  const { clearAuth } = useAuth();\n\n  // Global fetch interceptor\n  const originalFetch = globalThis.$fetch;\n\n  globalThis.$fetch = async (request, options) => {\n    try {\n      return await originalFetch(request, options);\n    } catch (error: any) {\n      // Handle 401 Unauthorized\n      if (error?.response?.status === 401) {\n        clearAuth();\n        navigateTo('/auth/login?expired=true');\n      }\n      throw error;\n    }\n  };\n});\n```\n\n```vue\n<!-- pages/auth/login.vue -->\n<script setup>\nconst route = useRoute();\nconst showExpiredMessage = computed(() => route.query.expired === 'true');\n</script>\n\n<template>\n  <div v-if=\"showExpiredMessage\" class=\"warning\">\n    Your session has expired. Please sign in again.\n  </div>\n  <!-- login form -->\n</template>\n```\n\n### Persist User Preference\n\nRemember user's email for faster login:\n\n```vue\n<!-- pages/auth/login.vue -->\n<script setup>\nconst { signIn, isLoading, error } = useAuth();\n\n// Persist email in localStorage\nconst rememberedEmail = useCookie('remembered_email', {\n  maxAge: 60 * 60 * 24 * 30, // 30 days\n});\n\nconst email = ref(rememberedEmail.value || '');\nconst password = ref('');\nconst rememberMe = ref(!!rememberedEmail.value);\n\nasync function handleSubmit() {\n  // Save or clear remembered email\n  if (rememberMe.value) {\n    rememberedEmail.value = email.value;\n  } else {\n    rememberedEmail.value = null;\n  }\n\n  const result = await signIn({ email: email.value, password: password.value });\n  // ...\n}\n</script>\n\n<template>\n  <form @submit.prevent=\"handleSubmit\">\n    <input v-model=\"email\" type=\"email\" placeholder=\"Email\" />\n    <input v-model=\"password\" type=\"password\" placeholder=\"Password\" />\n\n    <label>\n      <input v-model=\"rememberMe\" type=\"checkbox\" />\n      Remember my email\n    </label>\n\n    <button :disabled=\"isLoading\">Sign In</button>\n  </form>\n</template>\n```\n\n### Multi-tenant Authentication\n\nHandle authentication for multi-tenant SaaS applications:\n\n```typescript\n// nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: ['@dommidev10/nuxt-jwt-auth'],\n\n  auth: {\n    // Dynamic baseURL based on tenant\n    baseURL: '', // Set dynamically\n\n    endpoints: {\n      signIn: {\n        path: '/auth/login',\n        method: 'post',\n        body: {\n          email: 'email',\n          password: 'password',\n          // Include tenant in login\n          tenantId: 'tenantId',\n        },\n      },\n    },\n  },\n});\n```\n\n```vue\n<!-- pages/auth/login.vue -->\n<script setup>\nconst { signIn } = useAuth();\nconst route = useRoute();\n\n// Get tenant from subdomain or route\nconst tenant = computed(() => {\n  // subdomain: acme.app.com → 'acme'\n  const host = window.location.host;\n  const subdomain = host.split('.')[0];\n  return subdomain !== 'app' ? subdomain : route.query.tenant;\n});\n\nasync function handleLogin() {\n  await signIn({\n    email: email.value,\n    password: password.value,\n    tenantId: tenant.value,\n  });\n}\n</script>\n```\n\n### Protect API Routes\n\nUse the token in server API routes:\n\n```typescript\n// server/api/protected-data.get.ts\nexport default defineEventHandler(async (event) => {\n  const config = useRuntimeConfig();\n\n  // Get token from request cookies\n  const token = getCookie(event, 'auth.token');\n\n  if (!token) {\n    throw createError({\n      statusCode: 401,\n      message: 'Unauthorized',\n    });\n  }\n\n  // Forward request to backend with token\n  const data = await $fetch(`${config.apiBaseURL}/protected-endpoint`, {\n    headers: {\n      Authorization: `Bearer ${token}`,\n    },\n  });\n\n  return data;\n});\n```\n\n### Role-Based Access Control\n\nImplement role-based page protection:\n\n```typescript\n// middleware/admin.ts\nexport default defineNuxtRouteMiddleware(() => {\n  const { session, isAuthenticated } = useAuth();\n\n  if (!isAuthenticated.value) {\n    return navigateTo('/auth/login');\n  }\n\n  if (session.value?.role !== 'admin') {\n    return navigateTo('/dashboard?error=unauthorized');\n  }\n});\n```\n\n```vue\n<!-- pages/admin/users.vue -->\n<script setup>\ndefinePageMeta({\n  middleware: ['auth', 'admin'], // Apply both middlewares\n});\n</script>\n```\n\n### Conditional UI Based on Auth State\n\nShow different navigation based on authentication:\n\n```vue\n<!-- components/AppHeader.vue -->\n<script setup>\nconst { session, isAuthenticated, signOut } = useAuth();\n</script>\n\n<template>\n  <header>\n    <nav>\n      <NuxtLink to=\"/\">Home</NuxtLink>\n\n      <template v-if=\"isAuthenticated\">\n        <NuxtLink to=\"/dashboard\">Dashboard</NuxtLink>\n\n        <!-- Admin-only link -->\n        <NuxtLink v-if=\"session?.role === 'admin'\" to=\"/admin\">\n          Admin Panel\n        </NuxtLink>\n\n        <div class=\"user-menu\">\n          <span>{{ session?.email }}</span>\n          <button @click=\"signOut\">Logout</button>\n        </div>\n      </template>\n\n      <template v-else>\n        <NuxtLink to=\"/auth/login\">Login</NuxtLink>\n        <NuxtLink to=\"/auth/register\">Register</NuxtLink>\n      </template>\n    </nav>\n  </header>\n</template>\n```\n\n### Auto-Logout on Inactivity\n\nImplement automatic logout after period of inactivity:\n\n```typescript\n// plugins/auto-logout.client.ts\nexport default defineNuxtPlugin(() => {\n  const { isAuthenticated, signOut } = useAuth();\n\n  const INACTIVITY_TIMEOUT = 30 * 60 * 1000; // 30 minutes\n  let timeoutId: ReturnType<typeof setTimeout>;\n\n  function resetTimer() {\n    clearTimeout(timeoutId);\n\n    if (isAuthenticated.value) {\n      timeoutId = setTimeout(async () => {\n        await signOut();\n        navigateTo('/auth/login?reason=inactivity');\n      }, INACTIVITY_TIMEOUT);\n    }\n  }\n\n  // Reset timer on user activity\n  if (typeof window !== 'undefined') {\n    ['mousedown', 'keydown', 'touchstart', 'scroll'].forEach((event) => {\n      window.addEventListener(event, resetTimer, { passive: true });\n    });\n\n    // Initial timer\n    resetTimer();\n\n    // Watch for auth state changes\n    watch(isAuthenticated, (authenticated) => {\n      if (authenticated) {\n        resetTimer();\n      } else {\n        clearTimeout(timeoutId);\n      }\n    });\n  }\n});\n```\n\n---\n\n## JSON Pointers\n\nThe module uses JSON pointers (RFC 6901) to extract values from API responses. This allows you to work with any API response structure.\n\n### Basic Usage\n\n```typescript\n// API Response:\n{ \"accessToken\": \"eyJ...\" }\n\n// Pointer:\nsignInResponseTokenPointer: '/accessToken'\n// Extracts: \"eyJ...\"\n```\n\n### Nested Paths\n\n```typescript\n// API Response:\n{\n  \"data\": {\n    \"auth\": {\n      \"token\": \"eyJ...\"\n    }\n  }\n}\n\n// Pointer:\nsignInResponseTokenPointer: '/data/auth/token'\n// Extracts: \"eyJ...\"\n```\n\n### Root Extraction\n\n```typescript\n// API Response for getSession:\n{ \"id\": \"123\", \"email\": \"user@example.com\" }\n\n// Pointer:\nsession: {\n  dataResponsePointer: ''  // or '/'\n}\n// Returns entire response as session\n```\n\n### Nested Session\n\n```typescript\n// API Response for getSession:\n{\n  \"success\": true,\n  \"user\": { \"id\": \"123\", \"email\": \"user@example.com\" }\n}\n\n// Pointer:\nsession: {\n  dataResponsePointer: '/user'\n}\n// Returns only the user object as session\n```\n\n---\n\n## Body Mapping\n\nBody mapping allows you to adapt the module's field names to your API's expected field names.\n\n### Standard Mapping\n\n```typescript\n// Module sends:\nsignIn({ email: 'user@example.com', password: 'secret' })\n\n// With default config:\nbody: { email: 'email', password: 'password' }\n\n// API receives:\n{ \"email\": \"user@example.com\", \"password\": \"secret\" }\n```\n\n### Custom Field Names\n\n```typescript\n// Module sends:\nsignIn({ email: 'user@example.com', password: 'secret' })\n\n// With custom config:\nbody: { email: 'username', password: 'pwd' }\n\n// API receives:\n{ \"username\": \"user@example.com\", \"pwd\": \"secret\" }\n```\n\n### 2FA Example\n\n```typescript\n// Module sends internally:\n{ userId: 'user-123', code: '123456' }\n\n// With config:\nbody: { userId: 'user_id', code: 'otp_code' }\n\n// API receives:\n{ \"user_id\": \"user-123\", \"otp_code\": \"123456\" }\n```\n\n---\n\n## Backend Integration\n\nThe module is backend-agnostic. Here's what your API needs to implement:\n\n### Required Endpoints\n\n#### POST /auth/login\n\n**Request:**\n```json\n{\n  \"email\": \"user@example.com\",\n  \"password\": \"password123\"\n}\n```\n\n**Success Response (no 2FA):**\n```json\n{\n  \"accessToken\": \"eyJhbGciOiJIUzI1NiIs...\",\n  \"refreshToken\": \"eyJhbGciOiJIUzI1NiIs...\"\n}\n```\n\n**Success Response (2FA required):**\n```json\n{\n  \"requiresTwoFactor\": true,\n  \"userId\": \"user-123\",\n  \"email\": \"u***@example.com\",\n  \"expiresIn\": 300\n}\n```\n\n#### POST /auth/logout\n\n**Request:** Empty or with refresh token\n\n**Response:** 200 OK\n\n#### GET /auth/me\n\n**Headers:** `Authorization: Bearer <accessToken>`\n\n**Response:**\n```json\n{\n  \"id\": \"user-123\",\n  \"email\": \"user@example.com\",\n  \"firstName\": \"John\",\n  \"lastName\": \"Doe\",\n  \"role\": \"admin\",\n  \"twoFactorEnabled\": true\n}\n```\n\n### Optional Endpoints\n\n#### POST /auth/refresh\n\n**Request:**\n```json\n{\n  \"refreshToken\": \"eyJhbGciOiJIUzI1NiIs...\"\n}\n```\n\n**Response:**\n```json\n{\n  \"accessToken\": \"eyJhbGciOiJIUzI1NiIs...\",\n  \"refreshToken\": \"eyJhbGciOiJIUzI1NiIs...\"\n}\n```\n\n#### POST /auth/2fa/verify\n\n**Request:**\n```json\n{\n  \"userId\": \"user-123\",\n  \"code\": \"123456\"\n}\n```\n\n**Response:**\n```json\n{\n  \"accessToken\": \"eyJhbGciOiJIUzI1NiIs...\",\n  \"refreshToken\": \"eyJhbGciOiJIUzI1NiIs...\"\n}\n```\n\n#### POST /auth/2fa/resend\n\n**Request:**\n```json\n{\n  \"userId\": \"user-123\"\n}\n```\n\n**Response:**\n```json\n{\n  \"message\": \"Code sent successfully\",\n  \"cooldownSeconds\": 60\n}\n```\n\n#### POST /auth/forgot-password\n\n**Request:**\n```json\n{\n  \"email\": \"user@example.com\"\n}\n```\n\n**Response:**\n```json\n{\n  \"message\": \"Reset email sent\"\n}\n```\n\n#### POST /auth/reset-password\n\n**Request:**\n```json\n{\n  \"token\": \"reset-token-from-email\",\n  \"password\": \"newPassword123\",\n  \"passwordConfirm\": \"newPassword123\"\n}\n```\n\n**Response:**\n```json\n{\n  \"message\": \"Password reset successfully\"\n}\n```\n\n#### POST /auth/verify-email\n\n**Request:**\n```json\n{\n  \"token\": \"verification-token-from-email\"\n}\n```\n\n**Response:**\n```json\n{\n  \"message\": \"Email verified\",\n  \"verified\": true\n}\n```\n\n---\n\n## TypeScript\n\nThe module includes full TypeScript support.\n\n### Type Augmentation\n\nAdd session types for better autocompletion:\n\n```typescript\n// types/auth.d.ts\ndeclare module '@dommidev10/nuxt-jwt-auth' {\n  interface Session {\n    id: string;\n    email: string;\n    firstName: string;\n    lastName: string;\n    role: 'admin' | 'user' | 'guest';\n    twoFactorEnabled: boolean;\n    organizationId?: string;\n  }\n}\n\nexport {};\n```\n\n### Using Types\n\n```typescript\n// In your components\nconst { session } = useAuth();\n\n// session is typed as Session | null\nconsole.log(session.value?.role); // TypeScript knows about role\n```\n\n### Configuration Types\n\n```typescript\nimport type { AuthModuleOptions } from '@dommidev10/nuxt-jwt-auth';\n\nconst config: AuthModuleOptions = {\n  baseURL: 'https://api.example.com',\n  // Full autocompletion available\n};\n```\n\n---\n\n## Troubleshooting\n\n### Common Issues\n\n#### \"Auth is not defined\" or \"useAuth is not a function\"\n\nMake sure the module is properly added to your `nuxt.config.ts`:\n\n```typescript\nmodules: ['@dommidev10/nuxt-jwt-auth'],\n```\n\n#### Token not being sent with requests\n\nEnsure your API base URL matches and the token configuration is correct:\n\n```typescript\nauth: {\n  baseURL: 'https://api.example.com', // Must match your API\n  token: {\n    headerName: 'Authorization',\n    type: 'Bearer',\n  },\n}\n```\n\n#### 2FA/Password Reset/Email Verification composables not available\n\nThese composables are only registered when their feature is enabled:\n\n```typescript\nauth: {\n  twoFactor: { enabled: true },      // Enables use2FA()\n  passwordReset: { enabled: true },  // Enables usePasswordReset()\n  emailVerification: { enabled: true }, // Enables useEmailVerification()\n}\n```\n\n#### Refresh token not working\n\nCheck your refresh configuration:\n\n```typescript\nauth: {\n  refresh: {\n    enabled: true, // Must be true\n    endpoint: { path: '/auth/refresh', method: 'post' },\n    token: {\n      signInResponseRefreshTokenPointer: '/refreshToken',\n      refreshResponseTokenPointer: '/accessToken',\n    },\n  },\n}\n```\n\n#### Cookies not being set\n\nFor production with HTTPS:\n\n```typescript\nauth: {\n  token: {\n    secureCookieAttribute: true,\n    sameSiteAttribute: 'lax',\n  },\n}\n```\n\n### Debug Mode\n\nEnable debug mode to see detailed logs:\n\n```typescript\nauth: {\n  debug: true,\n}\n```\n\nThis will log:\n- API requests and responses\n- Token refresh scheduling\n- Middleware decisions\n- State changes\n\n---\n\n## Development\n\n### Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/dommidev10/nuxt-jwt-auth.git\ncd nuxt-jwt-auth\n\n# Install dependencies\npnpm install\n\n# Build the module\npnpm build\n\n# Run the playground\ncd playground && pnpm dev\n```\n\n### Scripts\n\n```bash\npnpm build      # Build the module\npnpm typecheck  # Run TypeScript checks\npnpm lint       # Run linter\npnpm test       # Run tests\n```\n\n### Project Structure\n\n```\nnuxt-jwt-auth/\n├── src/\n│   ├── module.ts                    # Module entry point\n│   ├── types.ts                     # TypeScript definitions\n│   └── runtime/\n│       ├── composables/\n│       │   ├── useAuth.ts           # Main auth composable\n│       │   ├── use2FA.ts            # 2FA composable\n│       │   ├── usePasswordReset.ts  # Password reset composable\n│       │   └── useEmailVerification.ts\n│       ├── middleware/\n│       │   └── auth.ts              # Route protection\n│       ├── plugins/\n│       │   └── auth.client.ts       # Client-side auto-refresh\n│       └── utils/\n│           └── extract-value.ts     # JSON pointer utilities\n├── playground/                       # Test application\n└── dist/                            # Built module\n```\n\n---\n\n## License\n\nMIT License\n\nCopyright (c) 2024 DommiDev10\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","readmeFilename":"README.md","_rev":"1-dfa64f516e05870bb7253274724604a3"}