npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dommidev10/nuxt-jwt-auth

v1.0.0

Published

Nuxt 3 authentication module with 2FA, password reset, and email verification

Readme

@dommidev10/nuxt-jwt-auth

A fully configurable Nuxt 3 authentication module with JWT support, refresh token rotation, two-factor authentication, password reset, and email verification.

Why Use This Module?

The Problem

Implementing authentication in Nuxt 3 applications typically requires:

  1. Repetitive boilerplate - Token management, cookie handling, API calls
  2. SSR complexity - Managing tokens across server and client contexts
  3. Security concerns - Proper token refresh, cookie security attributes
  4. Multiple flows - Login, logout, 2FA, password reset, email verification
  5. API flexibility - Every backend has different response structures

The Solution

@dommidev10/nuxt-jwt-auth provides a transport-agnostic authentication layer that:

| Challenge | Solution | |-----------|----------| | Different API field names | Body mapping - Map emailusername, passwordpwd | | Different response structures | JSON pointers - Extract tokens from any response path | | Token refresh complexity | Automatic refresh - Schedules refresh before expiration | | SSR hydration issues | Built-in SSR support - Cookies work seamlessly | | Multiple auth flows | Modular composables - Enable only what you need |

Who Should Use This?

Use this module if you:

  • Have an existing backend with JWT authentication
  • Need SSR-compatible authentication
  • Want configurable 2FA, password reset, or email verification
  • Don't want to write token management boilerplate
  • Need to adapt to various API response formats

Consider alternatives if you:

  • Use OAuth/Social login only (use @sidebase/nuxt-auth)
  • Have a Supabase/Firebase backend (use their official SDKs)
  • Need session-based authentication (not JWT)

Real-World Example: Before vs After

❌ Without This Module (150+ lines)

// composables/useAuth.ts - Manual implementation
export function useAuth() {
  const token = useCookie('auth.token');
  const refreshToken = useCookie('auth.refresh_token');
  const session = useState<User | null>('auth:session', () => null);
  const loading = useState('auth:loading', () => false);
  const error = useState<string | null>('auth:error', () => null);

  async function signIn(email: string, password: string) {
    loading.value = true;
    error.value = null;

    try {
      const response = await $fetch('/api/auth/login', {
        method: 'POST',
        body: { email, password },
      });

      // Handle 2FA case
      if (response.requiresTwoFactor) {
        return { requiresTwoFactor: true, userId: response.userId };
      }

      // Extract tokens (what if nested? what if different names?)
      token.value = response.accessToken;
      refreshToken.value = response.refreshToken;

      // Fetch session
      await getSession();

      // Schedule token refresh
      scheduleRefresh();

      return { requiresTwoFactor: false };
    } catch (err) {
      error.value = err.message;
      throw err;
    } finally {
      loading.value = false;
    }
  }

  async function getSession() { /* ... */ }
  async function refresh() { /* ... */ }
  function scheduleRefresh() { /* ... */ }
  async function signOut() { /* ... */ }

  // ... 100+ more lines for refresh logic, middleware, etc.

  return { token, session, signIn, signOut, /* ... */ };
}

✅ With This Module (10 lines config)

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@dommidev10/nuxt-jwt-auth'],

  auth: {
    baseURL: 'https://api.example.com',
    endpoints: {
      signIn: { path: '/auth/login', method: 'post' },
      signOut: { path: '/auth/logout', method: 'post' },
      getSession: { path: '/auth/me', method: 'get' },
    },
    token: { signInResponseTokenPointer: '/accessToken' },
  },
});
<!-- pages/login.vue - That's it! -->
<script setup>
const { signIn, isLoading, error } = useAuth(); // Auto-imported!

async function handleLogin() {
  const result = await signIn({ email, password });
  if (!result.requiresTwoFactor) navigateTo('/dashboard');
}
</script>

Key Benefits

1. Zero Lock-in

The module is a transport layer only. It doesn't:

  • Dictate your backend structure
  • Require specific database schemas
  • Handle email sending (your backend does)
  • Manage user storage

2. Incremental Adoption

Enable features as needed:

auth: {
  // Start simple
  twoFactor: { enabled: false },
  passwordReset: { enabled: false },
  emailVerification: { enabled: false },

  // Enable later when ready
  twoFactor: { enabled: true },
}

3. Works with Any Backend

Whether your API returns:

// Laravel Sanctum style
{ "token": "...", "user": { "id": 1 } }

// Express/NestJS style
{ "accessToken": "...", "refreshToken": "..." }

// Nested response
{ "data": { "auth": { "jwt": "..." } } }

Just configure the JSON pointers:

token: {
  signInResponseTokenPointer: '/data/auth/jwt', // Works!
}

4. Type-Safe

Full TypeScript support with autocompletion:

const { session } = useAuth();
session.value?.email  // ✓ Typed
session.value?.role   // ✓ Typed (with type augmentation)

Features

  • JWT Authentication - Complete token-based authentication with cookie storage
  • Refresh Token Rotation - Automatic token refresh before expiration
  • Two-Factor Authentication (2FA) - Configurable 2FA flow with code verification
  • Password Reset - Complete forgot/reset password flow
  • Email Verification - Email verification with resend capability
  • Route Protection - Middleware for protecting routes (global or per-page)
  • SSR Compatible - Works seamlessly with Nuxt's server-side rendering
  • Fully Configurable - JSON pointers for API response extraction, body mapping for field names
  • TypeScript Support - Full type definitions included

Table of Contents

Installation

# Using pnpm (recommended)
pnpm add @dommidev10/nuxt-jwt-auth

# Using npm
npm install @dommidev10/nuxt-jwt-auth

# Using yarn
yarn add @dommidev10/nuxt-jwt-auth

Quick Start

1. Add the module to your Nuxt config

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@dommidev10/nuxt-jwt-auth'],

  auth: {
    baseURL: 'https://api.example.com',
    endpoints: {
      signIn: { path: '/auth/login', method: 'post' },
      signOut: { path: '/auth/logout', method: 'post' },
      getSession: { path: '/auth/me', method: 'get' },
    },
    token: {
      signInResponseTokenPointer: '/accessToken',
    },
    pages: {
      login: '/auth/login',
      home: '/dashboard',
    },
  },
});

2. Use the composable in your components

<script setup lang="ts">
const { signIn, signOut, session, isAuthenticated, isLoading, error } = useAuth();

const email = ref('');
const password = ref('');

async function handleLogin() {
  try {
    const result = await signIn({ email: email.value, password: password.value });

    if (!result.requiresTwoFactor) {
      navigateTo('/dashboard');
    }
  } catch (err) {
    // Error is automatically set in the error ref
  }
}
</script>

<template>
  <div>
    <div v-if="isAuthenticated">
      <p>Welcome, {{ session?.email }}</p>
      <button @click="signOut">Logout</button>
    </div>

    <form v-else @submit.prevent="handleLogin">
      <input v-model="email" type="email" placeholder="Email" required />
      <input v-model="password" type="password" placeholder="Password" required />
      <div v-if="error" class="error">{{ error }}</div>
      <button :disabled="isLoading">
        {{ isLoading ? 'Signing in...' : 'Sign In' }}
      </button>
    </form>
  </div>
</template>

Configuration

Base Configuration

| Option | Type | Default | Description | |--------|------|---------|-------------| | baseURL | string | '' | Base URL for all API requests | | debug | boolean | false | Enable debug logging to console | | refreshOnFocusChanged | boolean | false | Refresh session when browser tab regains focus |

auth: {
  baseURL: 'https://api.example.com',
  debug: process.env.NODE_ENV === 'development',
  refreshOnFocusChanged: true,
}

Endpoints

Configure the main authentication endpoints.

auth: {
  endpoints: {
    signIn: {
      path: '/auth/login',
      method: 'post',
      body: {
        email: 'email',       // Maps 'email' → 'email' in request body
        password: 'password', // Maps 'password' → 'password' in request body
      },
    },
    signOut: {
      path: '/auth/logout',
      method: 'post',
    },
    getSession: {
      path: '/auth/me',
      method: 'get',
    },
  },
}

Custom Field Names

If your API expects different field names:

auth: {
  endpoints: {
    signIn: {
      path: '/auth/login',
      method: 'post',
      body: {
        email: 'username',  // Sends { username: '...' } instead of { email: '...' }
        password: 'pwd',    // Sends { pwd: '...' } instead of { password: '...' }
      },
    },
  },
}

Token Configuration

Configure how access tokens are handled.

| Option | Type | Default | Description | |--------|------|---------|-------------| | signInResponseTokenPointer | string | '/accessToken' | JSON pointer to extract token from login response | | cookieName | string | 'auth.token' | Cookie name for storing the token | | maxAgeInSeconds | number | 3600 | Cookie max age (1 hour) | | sameSiteAttribute | 'lax' \| 'strict' \| 'none' | 'lax' | Cookie SameSite attribute | | secureCookieAttribute | boolean \| undefined | undefined | Cookie Secure attribute (auto-detected if undefined) | | httpOnlyCookieAttribute | boolean | false | Cookie HttpOnly attribute | | type | string | 'Bearer' | Token type prefix in Authorization header | | headerName | string | 'Authorization' | HTTP header name for sending token |

auth: {
  token: {
    signInResponseTokenPointer: '/data/accessToken', // For nested responses
    cookieName: 'auth.token',
    maxAgeInSeconds: 3600,
    sameSiteAttribute: 'lax',
    secureCookieAttribute: true,  // Force secure cookies
    httpOnlyCookieAttribute: false,
    type: 'Bearer',
    headerName: 'Authorization',
  },
}

Refresh Token

Enable automatic token refresh before expiration.

| Option | Type | Default | Description | |--------|------|---------|-------------| | enabled | boolean | false | Enable refresh token functionality | | refreshOnlyToken | boolean | true | Only refresh token (not full session) | | refreshBeforeExpiryInSeconds | number | 300 | Refresh token X seconds before expiry |

auth: {
  refresh: {
    enabled: true,
    endpoint: {
      path: '/auth/refresh',
      method: 'post',
    },
    token: {
      signInResponseRefreshTokenPointer: '/refreshToken',
      refreshResponseTokenPointer: '/accessToken',
      refreshResponseRefreshTokenPointer: '/refreshToken', // For token rotation
      refreshRequestTokenPointer: 'refreshToken', // Key in request body
      cookieName: 'auth.refresh_token',
      maxAgeInSeconds: 604800, // 7 days
      sameSiteAttribute: 'lax',
      secureCookieAttribute: undefined,
      httpOnlyCookieAttribute: false,
    },
    refreshOnlyToken: true,
    refreshBeforeExpiryInSeconds: 300, // Refresh 5 min before expiry
  },
}

How it works:

  1. When user logs in, both access and refresh tokens are stored
  2. A timer is set to refresh the token 5 minutes before expiry
  3. When the timer fires, the module calls the refresh endpoint
  4. New tokens are stored and a new timer is set
  5. This continues seamlessly as long as the user is active

Two-Factor Authentication Configuration

Enable 2FA support for your application.

auth: {
  twoFactor: {
    enabled: true,

    endpoints: {
      verify: {
        path: '/auth/2fa/verify',
        method: 'post',
        body: {
          userId: 'userId',
          code: 'code',
        },
      },
      resend: {
        path: '/auth/2fa/resend',
        method: 'post',
        body: {
          userId: 'userId',
        },
      },
      // Optional: Enable/disable 2FA for user account
      enable: {
        path: '/auth/2fa/enable',
        method: 'post',
        body: {
          password: 'password',
        },
      },
      disable: {
        path: '/auth/2fa/disable',
        method: 'post',
        body: {
          password: 'password',
          code: 'code',
        },
      },
    },

    // Response extraction from login when 2FA is required
    response: {
      requiresTwoFactorPointer: '/requiresTwoFactor',
      userIdPointer: '/userId',
      emailPointer: '/email',       // Optional: masked email
      expiresInPointer: '/expiresIn', // Optional: code expiry time
    },

    // Response extraction from verify endpoint
    verifyResponse: {
      tokenPointer: '/accessToken',
      refreshTokenPointer: '/refreshToken',
    },

    // Response extraction from resend endpoint
    resendResponse: {
      messagePointer: '/message',
      cooldownPointer: '/cooldownSeconds',
    },

    // UI Configuration
    verifyPage: '/auth/verify-2fa',
    codeExpirationInSeconds: 300,  // 5 minutes
    resendCooldownInSeconds: 60,   // 1 minute cooldown between resends
  },
}

Expected API Response for Login (when 2FA required):

{
  "requiresTwoFactor": true,
  "userId": "user-123",
  "email": "u***@example.com",
  "expiresIn": 300
}

Password Reset Configuration

Enable password reset functionality.

auth: {
  passwordReset: {
    enabled: true,

    endpoints: {
      request: {
        path: '/auth/forgot-password',
        method: 'post',
        body: {
          email: 'email',
        },
      },
      reset: {
        path: '/auth/reset-password',
        method: 'post',
        body: {
          token: 'token',
          password: 'password',
          passwordConfirm: 'passwordConfirm',
        },
      },
    },

    // Response extraction
    requestResponse: {
      messagePointer: '/message',
    },
    resetResponse: {
      messagePointer: '/message',
      // Optional: Auto-login after password reset
      tokenPointer: '/accessToken',
      refreshTokenPointer: '/refreshToken',
    },

    // UI Configuration
    requestPage: '/auth/forgot-password',
    resetPage: '/auth/reset-password',
  },
}

Email Verification Configuration

Enable email verification functionality.

auth: {
  emailVerification: {
    enabled: true,

    endpoints: {
      verify: {
        path: '/auth/verify-email',
        method: 'post',
        body: {
          token: 'token',
        },
      },
      resend: {
        path: '/auth/resend-verification',
        method: 'post',
        body: {
          email: 'email',
        },
      },
    },

    // Response extraction
    verifyResponse: {
      messagePointer: '/message',
      verifiedPointer: '/verified',
    },
    resendResponse: {
      messagePointer: '/message',
    },

    // UI Configuration
    verifyPage: '/auth/verify-email',
  },
}

Session Configuration

Configure session data handling.

auth: {
  session: {
    // Define expected session data types (for TypeScript)
    dataType: {
      id: 'string',
      email: 'string',
      firstName: 'string',
      lastName: 'string',
      role: 'string',
      twoFactorEnabled: 'boolean',
    },
    // JSON pointer if session data is nested in response
    dataResponsePointer: '/user', // For { user: { id, email, ... } }
  },
}

Pages & Middleware Configuration

Configure authentication pages and route protection.

auth: {
  pages: {
    login: '/auth/login',   // Redirect here when not authenticated
    home: '/dashboard',     // Redirect here after login
  },

  globalMiddleware: true, // Apply auth middleware to all routes

  publicRoutes: [
    '/',                    // Exact match
    '/about',
    '/auth/*',              // Wildcard: all /auth/* routes
    '^/blog/\\d+$',         // Regex: /blog/123 but not /blog/abc
  ],
}

Composables

useAuth

The main authentication composable. Always available.

const {
  // Reactive State
  token,              // Ref<string | null> - Current access token
  refreshToken,       // Ref<string | null> - Current refresh token
  session,            // Ref<Session | null> - User session data
  isAuthenticated,    // ComputedRef<boolean> - Is user logged in
  isLoading,          // ComputedRef<boolean> - Is operation in progress
  error,              // ComputedRef<string | null> - Last error message

  // Actions
  signIn,             // (credentials) => Promise<SignInResult>
  signOut,            // () => Promise<void>
  getSession,         // () => Promise<Session | null>
  refresh,            // () => Promise<boolean>
  completeSignIn,     // (accessToken, refreshToken?) => Promise<void>
  clearAuth,          // () => void
} = useAuth();

signIn(credentials)

Authenticate user with credentials.

interface SignInResult {
  requiresTwoFactor: boolean;
  userId?: string;
  email?: string;
  expiresIn?: number;
}

const result = await signIn({
  email: '[email protected]',
  password: 'password123',
});

if (result.requiresTwoFactor) {
  // Redirect to 2FA verification
  navigateTo('/auth/verify-2fa');
} else {
  // Login successful
  navigateTo('/dashboard');
}

signOut()

Log out the current user.

await signOut();
// User is logged out, tokens cleared, redirected to login

getSession()

Fetch the current user's session data.

const session = await getSession();
console.log(session?.email, session?.role);

refresh()

Manually refresh the access token.

const success = await refresh();
if (!success) {
  // Refresh failed, user needs to login again
}

completeSignIn(accessToken, refreshToken?)

Complete authentication after 2FA verification. Usually called internally by use2FA.

await completeSignIn('new-access-token', 'new-refresh-token');

clearAuth()

Clear all authentication state without calling logout endpoint.

clearAuth();

use2FA

Two-factor authentication composable. Available when twoFactor.enabled: true.

const {
  // Reactive State
  pending2FA,        // Readonly<Ref<Pending2FA | null>> - Current 2FA state
  is2FAExpired,      // ComputedRef<boolean> - Has the code expired
  timeRemaining,     // ComputedRef<number> - Seconds until expiry
  canResend,         // ComputedRef<boolean> - Can resend code
  resendCooldown,    // Readonly<Ref<number>> - Seconds until can resend
  isLoading,         // ComputedRef<boolean>
  error,             // ComputedRef<string | null>

  // Actions
  initiate2FA,       // (userId, email?, expiresIn?) => void
  verify2FA,         // (code) => Promise<void>
  resend2FA,         // () => Promise<{ message?, cooldownSeconds? }>
  cancel2FA,         // () => void
} = use2FA();

Pending2FA Interface

interface Pending2FA {
  userId: string;
  email?: string;
  expiresAt: number;  // Timestamp when code expires
}

initiate2FA(userId, email?, expiresIn?)

Start the 2FA verification process. Called automatically by signIn when 2FA is required.

initiate2FA('user-123', 'u***@example.com', 300);

verify2FA(code)

Verify the 2FA code and complete authentication.

try {
  await verify2FA('123456');
  // User is now fully authenticated
  navigateTo('/dashboard');
} catch (err) {
  // Invalid code, error is set automatically
}

resend2FA()

Request a new 2FA code.

if (canResend.value) {
  const { message, cooldownSeconds } = await resend2FA();
  // New code sent, cooldown timer started
}

cancel2FA()

Cancel the 2FA process and clear state.

cancel2FA();
navigateTo('/auth/login');

usePasswordReset

Password reset composable. Available when passwordReset.enabled: true.

const {
  // Reactive State
  isLoading,         // Readonly<Ref<boolean>>
  error,             // Readonly<Ref<string | null>>
  emailSent,         // Readonly<Ref<boolean>> - Reset email sent successfully

  // Actions
  requestReset,      // (email) => Promise<{ message? }>
  resetPassword,     // (token, password, passwordConfirm?) => Promise<{ message? }>
  clearState,        // () => void
} = usePasswordReset();

requestReset(email)

Request a password reset email.

try {
  const { message } = await requestReset('[email protected]');
  // emailSent.value is now true
  console.log(message); // "Check your email for reset instructions"
} catch (err) {
  // Error handled automatically
}

resetPassword(token, password, passwordConfirm?)

Reset the password using the token from the email link.

const route = useRoute();
const token = route.query.token as string;

try {
  await resetPassword(token, 'newPassword123', 'newPassword123');
  // Password reset successful
  // If auto-login is configured, user is now authenticated
  navigateTo('/dashboard');
} catch (err) {
  // Invalid token or password requirements not met
}

useEmailVerification

Email verification composable. Available when emailVerification.enabled: true.

const {
  // Reactive State
  isLoading,              // Readonly<Ref<boolean>>
  error,                  // Readonly<Ref<string | null>>
  isVerified,             // Readonly<Ref<boolean>>

  // Actions
  verifyEmail,            // (token) => Promise<{ message?, verified? }>
  resendVerification,     // (email?) => Promise<{ message? }>
  clearState,             // () => void
} = useEmailVerification();

verifyEmail(token)

Verify the email using the token from the verification link.

const route = useRoute();
const token = route.query.token as string;

try {
  const { message, verified } = await verifyEmail(token);
  // isVerified.value is now true
  console.log(message); // "Email verified successfully"
} catch (err) {
  // Invalid or expired token
}

resendVerification(email?)

Resend the verification email.

try {
  const { message } = await resendVerification('[email protected]');
  console.log(message); // "Verification email sent"
} catch (err) {
  // Error handled automatically
}

Route Protection

The module includes middleware for protecting routes.

Global Middleware

Apply to all routes automatically:

// nuxt.config.ts
auth: {
  globalMiddleware: true,
  publicRoutes: [
    '/',
    '/auth/*',
    '/about',
    '/pricing',
  ],
}

Per-Page Middleware

Apply to specific pages:

// nuxt.config.ts
auth: {
  globalMiddleware: false, // Disable global
}
<!-- pages/dashboard.vue -->
<script setup>
definePageMeta({
  middleware: 'auth',
});
</script>

Public Route Patterns

The module supports three types of route matching:

| Pattern | Example | Matches | |---------|---------|---------| | Exact | /about | Only /about | | Wildcard | /auth/* | /auth/login, /auth/register, etc. | | Regex | ^/blog/\d+$ | /blog/123 but not /blog/abc |

publicRoutes: [
  '/',                     // Exact: only root
  '/about',                // Exact: only /about
  '/auth/*',               // Wildcard: all auth routes
  '/api/*',                // Wildcard: all API routes
  '^/posts/\\d+$',         // Regex: /posts/123
  '^/users/[a-z]+/profile$', // Regex: /users/john/profile
],

Redirect Behavior

| Scenario | Action | |----------|--------| | Unauthenticated → Protected | Redirect to login with ?redirect=/original-path | | Authenticated → Auth page | Redirect to home | | Any → Public route | Allow access |


Complete Examples

Login Page

<!-- pages/auth/login.vue -->
<script setup lang="ts">
definePageMeta({
  layout: 'auth',
});

const { signIn, isLoading, error } = useAuth();
const { initiate2FA } = use2FA();
const route = useRoute();

const email = ref('');
const password = ref('');

async function handleSubmit() {
  try {
    const result = await signIn({
      email: email.value,
      password: password.value,
    });

    if (result.requiresTwoFactor) {
      // Store 2FA state and redirect
      initiate2FA(result.userId!, result.email, result.expiresIn);
      navigateTo('/auth/verify-2fa');
    } else {
      // Successful login - redirect to original destination or home
      const redirect = route.query.redirect as string;
      navigateTo(redirect || '/dashboard');
    }
  } catch (err) {
    // Error is automatically set in error ref
  }
}
</script>

<template>
  <div class="login-page">
    <h1>Sign In</h1>

    <form @submit.prevent="handleSubmit">
      <div class="form-group">
        <label for="email">Email</label>
        <input
          id="email"
          v-model="email"
          type="email"
          placeholder="[email protected]"
          required
          autocomplete="email"
        />
      </div>

      <div class="form-group">
        <label for="password">Password</label>
        <input
          id="password"
          v-model="password"
          type="password"
          placeholder="••••••••"
          required
          autocomplete="current-password"
        />
      </div>

      <div v-if="error" class="error-message">
        {{ error }}
      </div>

      <button type="submit" :disabled="isLoading">
        {{ isLoading ? 'Signing in...' : 'Sign In' }}
      </button>

      <div class="links">
        <NuxtLink to="/auth/forgot-password">Forgot password?</NuxtLink>
        <NuxtLink to="/auth/register">Create account</NuxtLink>
      </div>
    </form>
  </div>
</template>

2FA Verification Page

<!-- pages/auth/verify-2fa.vue -->
<script setup lang="ts">
definePageMeta({
  layout: 'auth',
});

const {
  pending2FA,
  verify2FA,
  resend2FA,
  cancel2FA,
  is2FAExpired,
  timeRemaining,
  canResend,
  resendCooldown,
  isLoading,
  error,
} = use2FA();

const code = ref('');
const resendMessage = ref('');

// Redirect if no pending 2FA
onMounted(() => {
  if (!pending2FA.value) {
    navigateTo('/auth/login');
  }
});

// Format time remaining
const formattedTime = computed(() => {
  const minutes = Math.floor(timeRemaining.value / 60);
  const seconds = timeRemaining.value % 60;
  return `${minutes}:${seconds.toString().padStart(2, '0')}`;
});

async function handleVerify() {
  try {
    await verify2FA(code.value);
    navigateTo('/dashboard');
  } catch (err) {
    code.value = ''; // Clear code on error
  }
}

async function handleResend() {
  try {
    const { message } = await resend2FA();
    resendMessage.value = message || 'Code sent!';
    setTimeout(() => {
      resendMessage.value = '';
    }, 3000);
  } catch (err) {
    // Error handled automatically
  }
}

function handleCancel() {
  cancel2FA();
  navigateTo('/auth/login');
}
</script>

<template>
  <div class="verify-2fa-page">
    <h1>Two-Factor Authentication</h1>

    <div v-if="pending2FA" class="content">
      <p>
        Enter the verification code sent to
        <strong>{{ pending2FA.email || 'your email' }}</strong>
      </p>

      <div v-if="is2FAExpired" class="expired-message">
        <p>Your code has expired.</p>
        <button @click="handleResend" :disabled="!canResend || isLoading">
          Request New Code
        </button>
      </div>

      <form v-else @submit.prevent="handleVerify">
        <div class="form-group">
          <label for="code">Verification Code</label>
          <input
            id="code"
            v-model="code"
            type="text"
            inputmode="numeric"
            pattern="[0-9]*"
            maxlength="6"
            placeholder="000000"
            required
            autocomplete="one-time-code"
          />
        </div>

        <p class="timer">Code expires in {{ formattedTime }}</p>

        <div v-if="error" class="error-message">
          {{ error }}
        </div>

        <div v-if="resendMessage" class="success-message">
          {{ resendMessage }}
        </div>

        <button type="submit" :disabled="isLoading || code.length < 6">
          {{ isLoading ? 'Verifying...' : 'Verify' }}
        </button>

        <div class="actions">
          <button
            type="button"
            @click="handleResend"
            :disabled="!canResend || isLoading"
            class="link-button"
          >
            {{ canResend ? 'Resend code' : `Resend in ${resendCooldown}s` }}
          </button>

          <button
            type="button"
            @click="handleCancel"
            class="link-button"
          >
            Cancel
          </button>
        </div>
      </form>
    </div>

    <div v-else class="no-session">
      <p>No verification session found.</p>
      <NuxtLink to="/auth/login">Return to Login</NuxtLink>
    </div>
  </div>
</template>

Forgot Password Page

<!-- pages/auth/forgot-password.vue -->
<script setup lang="ts">
definePageMeta({
  layout: 'auth',
});

const { requestReset, emailSent, isLoading, error, clearState } = usePasswordReset();

const email = ref('');

async function handleSubmit() {
  try {
    await requestReset(email.value);
    // emailSent.value is now true
  } catch (err) {
    // Error handled automatically
  }
}

// Clear state when leaving page
onUnmounted(() => {
  clearState();
});
</script>

<template>
  <div class="forgot-password-page">
    <h1>Forgot Password</h1>

    <div v-if="emailSent" class="success-state">
      <div class="icon">✓</div>
      <h2>Check your email</h2>
      <p>
        We've sent a password reset link to
        <strong>{{ email }}</strong>
      </p>
      <p class="note">
        Didn't receive the email? Check your spam folder or
        <button @click="emailSent = false" class="link-button">
          try again
        </button>
      </p>
      <NuxtLink to="/auth/login" class="back-link">
        Back to Login
      </NuxtLink>
    </div>

    <form v-else @submit.prevent="handleSubmit">
      <p>Enter your email address and we'll send you a link to reset your password.</p>

      <div class="form-group">
        <label for="email">Email</label>
        <input
          id="email"
          v-model="email"
          type="email"
          placeholder="[email protected]"
          required
          autocomplete="email"
        />
      </div>

      <div v-if="error" class="error-message">
        {{ error }}
      </div>

      <button type="submit" :disabled="isLoading">
        {{ isLoading ? 'Sending...' : 'Send Reset Link' }}
      </button>

      <NuxtLink to="/auth/login" class="back-link">
        Back to Login
      </NuxtLink>
    </form>
  </div>
</template>

Reset Password Page

<!-- pages/auth/reset-password.vue -->
<script setup lang="ts">
definePageMeta({
  layout: 'auth',
});

const { resetPassword, isLoading, error, clearState } = usePasswordReset();
const route = useRoute();

const password = ref('');
const passwordConfirm = ref('');
const success = ref(false);

const token = computed(() => route.query.token as string);

// Redirect if no token
onMounted(() => {
  if (!token.value) {
    navigateTo('/auth/forgot-password');
  }
});

async function handleSubmit() {
  if (password.value !== passwordConfirm.value) {
    return;
  }

  try {
    await resetPassword(token.value, password.value, passwordConfirm.value);
    success.value = true;

    // If auto-login is configured, redirect to dashboard
    // Otherwise, redirect to login
    setTimeout(() => {
      navigateTo('/auth/login?reset=success');
    }, 2000);
  } catch (err) {
    // Error handled automatically
  }
}

const passwordsMatch = computed(() => {
  return password.value === passwordConfirm.value;
});

onUnmounted(() => {
  clearState();
});
</script>

<template>
  <div class="reset-password-page">
    <h1>Reset Password</h1>

    <div v-if="success" class="success-state">
      <div class="icon">✓</div>
      <h2>Password Reset!</h2>
      <p>Your password has been successfully reset.</p>
      <p>Redirecting to login...</p>
    </div>

    <div v-else-if="!token" class="no-token">
      <p>Invalid or missing reset token.</p>
      <NuxtLink to="/auth/forgot-password">
        Request a new reset link
      </NuxtLink>
    </div>

    <form v-else @submit.prevent="handleSubmit">
      <div class="form-group">
        <label for="password">New Password</label>
        <input
          id="password"
          v-model="password"
          type="password"
          placeholder="••••••••"
          required
          minlength="8"
          autocomplete="new-password"
        />
      </div>

      <div class="form-group">
        <label for="passwordConfirm">Confirm Password</label>
        <input
          id="passwordConfirm"
          v-model="passwordConfirm"
          type="password"
          placeholder="••••••••"
          required
          autocomplete="new-password"
        />
        <p v-if="passwordConfirm && !passwordsMatch" class="field-error">
          Passwords do not match
        </p>
      </div>

      <div v-if="error" class="error-message">
        {{ error }}
      </div>

      <button type="submit" :disabled="isLoading || !passwordsMatch">
        {{ isLoading ? 'Resetting...' : 'Reset Password' }}
      </button>
    </form>
  </div>
</template>

Email Verification Page

<!-- pages/auth/verify-email.vue -->
<script setup lang="ts">
definePageMeta({
  layout: 'auth',
});

const { verifyEmail, resendVerification, isVerified, isLoading, error, clearState } = useEmailVerification();
const { session } = useAuth();
const route = useRoute();

const resendEmail = ref('');
const resendSent = ref(false);

const token = computed(() => route.query.token as string);

// Auto-verify on page load
onMounted(async () => {
  if (token.value) {
    try {
      await verifyEmail(token.value);
    } catch (err) {
      // Error handled automatically
    }
  }
});

async function handleResend() {
  const email = resendEmail.value || session.value?.email;
  if (!email) return;

  try {
    await resendVerification(email);
    resendSent.value = true;
  } catch (err) {
    // Error handled automatically
  }
}

onUnmounted(() => {
  clearState();
});
</script>

<template>
  <div class="verify-email-page">
    <h1>Email Verification</h1>

    <div v-if="isLoading" class="loading-state">
      <div class="spinner"></div>
      <p>Verifying your email...</p>
    </div>

    <div v-else-if="isVerified" class="success-state">
      <div class="icon">✓</div>
      <h2>Email Verified!</h2>
      <p>Your email has been successfully verified.</p>
      <NuxtLink to="/dashboard" class="button">
        Continue to Dashboard
      </NuxtLink>
    </div>

    <div v-else-if="error" class="error-state">
      <div class="icon">✗</div>
      <h2>Verification Failed</h2>
      <p>{{ error }}</p>

      <div v-if="resendSent" class="resend-success">
        <p>A new verification link has been sent to your email.</p>
      </div>

      <div v-else class="resend-form">
        <p>Enter your email to receive a new verification link:</p>
        <div class="form-group">
          <input
            v-model="resendEmail"
            type="email"
            placeholder="[email protected]"
            :value="session?.email"
          />
          <button @click="handleResend" :disabled="isLoading">
            Resend Link
          </button>
        </div>
      </div>

      <NuxtLink to="/auth/login" class="back-link">
        Back to Login
      </NuxtLink>
    </div>

    <div v-else class="no-token">
      <p>No verification token found.</p>
      <NuxtLink to="/">Go Home</NuxtLink>
    </div>
  </div>
</template>

Protected Dashboard

<!-- pages/dashboard.vue -->
<script setup lang="ts">
// This page is protected by the auth middleware

const { session, signOut, isLoading } = useAuth();

async function handleLogout() {
  await signOut();
  // User is automatically redirected to login
}
</script>

<template>
  <div class="dashboard">
    <header>
      <h1>Dashboard</h1>
      <div class="user-info">
        <span>{{ session?.email }}</span>
        <button @click="handleLogout" :disabled="isLoading">
          {{ isLoading ? 'Logging out...' : 'Logout' }}
        </button>
      </div>
    </header>

    <main>
      <div class="welcome-card">
        <h2>Welcome, {{ session?.firstName || 'User' }}!</h2>
        <p>You are logged in as {{ session?.role || 'member' }}.</p>
      </div>

      <div class="user-details">
        <h3>Your Profile</h3>
        <dl>
          <dt>Email</dt>
          <dd>{{ session?.email }}</dd>

          <dt>User ID</dt>
          <dd>{{ session?.id }}</dd>

          <dt>2FA Enabled</dt>
          <dd>{{ session?.twoFactorEnabled ? 'Yes' : 'No' }}</dd>
        </dl>
      </div>
    </main>
  </div>
</template>

Practical Recipes

Custom API Headers

Add custom headers to all auth requests (e.g., tenant ID, API version):

// composables/useAuthFetch.ts
export function useAuthFetch() {
  const { token } = useAuth();
  const config = useRuntimeConfig();

  return $fetch.create({
    baseURL: config.public.auth.baseURL,
    onRequest({ options }) {
      // Add auth header
      if (token.value) {
        options.headers = {
          ...options.headers,
          Authorization: `Bearer ${token.value}`,
        };
      }

      // Add custom headers
      options.headers = {
        ...options.headers,
        'X-Tenant-ID': 'my-tenant',
        'X-API-Version': '2024-01',
      };
    },
  });
}

Handle Token Expiration

Create a global error handler for expired tokens:

// plugins/auth-error-handler.client.ts
export default defineNuxtPlugin(() => {
  const { clearAuth } = useAuth();

  // Global fetch interceptor
  const originalFetch = globalThis.$fetch;

  globalThis.$fetch = async (request, options) => {
    try {
      return await originalFetch(request, options);
    } catch (error: any) {
      // Handle 401 Unauthorized
      if (error?.response?.status === 401) {
        clearAuth();
        navigateTo('/auth/login?expired=true');
      }
      throw error;
    }
  };
});
<!-- pages/auth/login.vue -->
<script setup>
const route = useRoute();
const showExpiredMessage = computed(() => route.query.expired === 'true');
</script>

<template>
  <div v-if="showExpiredMessage" class="warning">
    Your session has expired. Please sign in again.
  </div>
  <!-- login form -->
</template>

Persist User Preference

Remember user's email for faster login:

<!-- pages/auth/login.vue -->
<script setup>
const { signIn, isLoading, error } = useAuth();

// Persist email in localStorage
const rememberedEmail = useCookie('remembered_email', {
  maxAge: 60 * 60 * 24 * 30, // 30 days
});

const email = ref(rememberedEmail.value || '');
const password = ref('');
const rememberMe = ref(!!rememberedEmail.value);

async function handleSubmit() {
  // Save or clear remembered email
  if (rememberMe.value) {
    rememberedEmail.value = email.value;
  } else {
    rememberedEmail.value = null;
  }

  const result = await signIn({ email: email.value, password: password.value });
  // ...
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="email" type="email" placeholder="Email" />
    <input v-model="password" type="password" placeholder="Password" />

    <label>
      <input v-model="rememberMe" type="checkbox" />
      Remember my email
    </label>

    <button :disabled="isLoading">Sign In</button>
  </form>
</template>

Multi-tenant Authentication

Handle authentication for multi-tenant SaaS applications:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@dommidev10/nuxt-jwt-auth'],

  auth: {
    // Dynamic baseURL based on tenant
    baseURL: '', // Set dynamically

    endpoints: {
      signIn: {
        path: '/auth/login',
        method: 'post',
        body: {
          email: 'email',
          password: 'password',
          // Include tenant in login
          tenantId: 'tenantId',
        },
      },
    },
  },
});
<!-- pages/auth/login.vue -->
<script setup>
const { signIn } = useAuth();
const route = useRoute();

// Get tenant from subdomain or route
const tenant = computed(() => {
  // subdomain: acme.app.com → 'acme'
  const host = window.location.host;
  const subdomain = host.split('.')[0];
  return subdomain !== 'app' ? subdomain : route.query.tenant;
});

async function handleLogin() {
  await signIn({
    email: email.value,
    password: password.value,
    tenantId: tenant.value,
  });
}
</script>

Protect API Routes

Use the token in server API routes:

// server/api/protected-data.get.ts
export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();

  // Get token from request cookies
  const token = getCookie(event, 'auth.token');

  if (!token) {
    throw createError({
      statusCode: 401,
      message: 'Unauthorized',
    });
  }

  // Forward request to backend with token
  const data = await $fetch(`${config.apiBaseURL}/protected-endpoint`, {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });

  return data;
});

Role-Based Access Control

Implement role-based page protection:

// middleware/admin.ts
export default defineNuxtRouteMiddleware(() => {
  const { session, isAuthenticated } = useAuth();

  if (!isAuthenticated.value) {
    return navigateTo('/auth/login');
  }

  if (session.value?.role !== 'admin') {
    return navigateTo('/dashboard?error=unauthorized');
  }
});
<!-- pages/admin/users.vue -->
<script setup>
definePageMeta({
  middleware: ['auth', 'admin'], // Apply both middlewares
});
</script>

Conditional UI Based on Auth State

Show different navigation based on authentication:

<!-- components/AppHeader.vue -->
<script setup>
const { session, isAuthenticated, signOut } = useAuth();
</script>

<template>
  <header>
    <nav>
      <NuxtLink to="/">Home</NuxtLink>

      <template v-if="isAuthenticated">
        <NuxtLink to="/dashboard">Dashboard</NuxtLink>

        <!-- Admin-only link -->
        <NuxtLink v-if="session?.role === 'admin'" to="/admin">
          Admin Panel
        </NuxtLink>

        <div class="user-menu">
          <span>{{ session?.email }}</span>
          <button @click="signOut">Logout</button>
        </div>
      </template>

      <template v-else>
        <NuxtLink to="/auth/login">Login</NuxtLink>
        <NuxtLink to="/auth/register">Register</NuxtLink>
      </template>
    </nav>
  </header>
</template>

Auto-Logout on Inactivity

Implement automatic logout after period of inactivity:

// plugins/auto-logout.client.ts
export default defineNuxtPlugin(() => {
  const { isAuthenticated, signOut } = useAuth();

  const INACTIVITY_TIMEOUT = 30 * 60 * 1000; // 30 minutes
  let timeoutId: ReturnType<typeof setTimeout>;

  function resetTimer() {
    clearTimeout(timeoutId);

    if (isAuthenticated.value) {
      timeoutId = setTimeout(async () => {
        await signOut();
        navigateTo('/auth/login?reason=inactivity');
      }, INACTIVITY_TIMEOUT);
    }
  }

  // Reset timer on user activity
  if (typeof window !== 'undefined') {
    ['mousedown', 'keydown', 'touchstart', 'scroll'].forEach((event) => {
      window.addEventListener(event, resetTimer, { passive: true });
    });

    // Initial timer
    resetTimer();

    // Watch for auth state changes
    watch(isAuthenticated, (authenticated) => {
      if (authenticated) {
        resetTimer();
      } else {
        clearTimeout(timeoutId);
      }
    });
  }
});

JSON Pointers

The module uses JSON pointers (RFC 6901) to extract values from API responses. This allows you to work with any API response structure.

Basic Usage

// API Response:
{ "accessToken": "eyJ..." }

// Pointer:
signInResponseTokenPointer: '/accessToken'
// Extracts: "eyJ..."

Nested Paths

// API Response:
{
  "data": {
    "auth": {
      "token": "eyJ..."
    }
  }
}

// Pointer:
signInResponseTokenPointer: '/data/auth/token'
// Extracts: "eyJ..."

Root Extraction

// API Response for getSession:
{ "id": "123", "email": "[email protected]" }

// Pointer:
session: {
  dataResponsePointer: ''  // or '/'
}
// Returns entire response as session

Nested Session

// API Response for getSession:
{
  "success": true,
  "user": { "id": "123", "email": "[email protected]" }
}

// Pointer:
session: {
  dataResponsePointer: '/user'
}
// Returns only the user object as session

Body Mapping

Body mapping allows you to adapt the module's field names to your API's expected field names.

Standard Mapping

// Module sends:
signIn({ email: '[email protected]', password: 'secret' })

// With default config:
body: { email: 'email', password: 'password' }

// API receives:
{ "email": "[email protected]", "password": "secret" }

Custom Field Names

// Module sends:
signIn({ email: '[email protected]', password: 'secret' })

// With custom config:
body: { email: 'username', password: 'pwd' }

// API receives:
{ "username": "[email protected]", "pwd": "secret" }

2FA Example

// Module sends internally:
{ userId: 'user-123', code: '123456' }

// With config:
body: { userId: 'user_id', code: 'otp_code' }

// API receives:
{ "user_id": "user-123", "otp_code": "123456" }

Backend Integration

The module is backend-agnostic. Here's what your API needs to implement:

Required Endpoints

POST /auth/login

Request:

{
  "email": "[email protected]",
  "password": "password123"
}

Success Response (no 2FA):

{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}

Success Response (2FA required):

{
  "requiresTwoFactor": true,
  "userId": "user-123",
  "email": "u***@example.com",
  "expiresIn": 300
}

POST /auth/logout

Request: Empty or with refresh token

Response: 200 OK

GET /auth/me

Headers: Authorization: Bearer <accessToken>

Response:

{
  "id": "user-123",
  "email": "[email protected]",
  "firstName": "John",
  "lastName": "Doe",
  "role": "admin",
  "twoFactorEnabled": true
}

Optional Endpoints

POST /auth/refresh

Request:

{
  "refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}

Response:

{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}

POST /auth/2fa/verify

Request:

{
  "userId": "user-123",
  "code": "123456"
}

Response:

{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}

POST /auth/2fa/resend

Request:

{
  "userId": "user-123"
}

Response:

{
  "message": "Code sent successfully",
  "cooldownSeconds": 60
}

POST /auth/forgot-password

Request:

{
  "email": "[email protected]"
}

Response:

{
  "message": "Reset email sent"
}

POST /auth/reset-password

Request:

{
  "token": "reset-token-from-email",
  "password": "newPassword123",
  "passwordConfirm": "newPassword123"
}

Response:

{
  "message": "Password reset successfully"
}

POST /auth/verify-email

Request:

{
  "token": "verification-token-from-email"
}

Response:

{
  "message": "Email verified",
  "verified": true
}

TypeScript

The module includes full TypeScript support.

Type Augmentation

Add session types for better autocompletion:

// types/auth.d.ts
declare module '@dommidev10/nuxt-jwt-auth' {
  interface Session {
    id: string;
    email: string;
    firstName: string;
    lastName: string;
    role: 'admin' | 'user' | 'guest';
    twoFactorEnabled: boolean;
    organizationId?: string;
  }
}

export {};

Using Types

// In your components
const { session } = useAuth();

// session is typed as Session | null
console.log(session.value?.role); // TypeScript knows about role

Configuration Types

import type { AuthModuleOptions } from '@dommidev10/nuxt-jwt-auth';

const config: AuthModuleOptions = {
  baseURL: 'https://api.example.com',
  // Full autocompletion available
};

Troubleshooting

Common Issues

"Auth is not defined" or "useAuth is not a function"

Make sure the module is properly added to your nuxt.config.ts:

modules: ['@dommidev10/nuxt-jwt-auth'],

Token not being sent with requests

Ensure your API base URL matches and the token configuration is correct:

auth: {
  baseURL: 'https://api.example.com', // Must match your API
  token: {
    headerName: 'Authorization',
    type: 'Bearer',
  },
}

2FA/Password Reset/Email Verification composables not available

These composables are only registered when their feature is enabled:

auth: {
  twoFactor: { enabled: true },      // Enables use2FA()
  passwordReset: { enabled: true },  // Enables usePasswordReset()
  emailVerification: { enabled: true }, // Enables useEmailVerification()
}

Refresh token not working

Check your refresh configuration:

auth: {
  refresh: {
    enabled: true, // Must be true
    endpoint: { path: '/auth/refresh', method: 'post' },
    token: {
      signInResponseRefreshTokenPointer: '/refreshToken',
      refreshResponseTokenPointer: '/accessToken',
    },
  },
}

Cookies not being set

For production with HTTPS:

auth: {
  token: {
    secureCookieAttribute: true,
    sameSiteAttribute: 'lax',
  },
}

Debug Mode

Enable debug mode to see detailed logs:

auth: {
  debug: true,
}

This will log:

  • API requests and responses
  • Token refresh scheduling
  • Middleware decisions
  • State changes

Development

Setup

# Clone the repository
git clone https://github.com/dommidev10/nuxt-jwt-auth.git
cd nuxt-jwt-auth

# Install dependencies
pnpm install

# Build the module
pnpm build

# Run the playground
cd playground && pnpm dev

Scripts

pnpm build      # Build the module
pnpm typecheck  # Run TypeScript checks
pnpm lint       # Run linter
pnpm test       # Run tests

Project Structure

nuxt-jwt-auth/
├── src/
│   ├── module.ts                    # Module entry point
│   ├── types.ts                     # TypeScript definitions
│   └── runtime/
│       ├── composables/
│       │   ├── useAuth.ts           # Main auth composable
│       │   ├── use2FA.ts            # 2FA composable
│       │   ├── usePasswordReset.ts  # Password reset composable
│       │   └── useEmailVerification.ts
│       ├── middleware/
│       │   └── auth.ts              # Route protection
│       ├── plugins/
│       │   └── auth.client.ts       # Client-side auto-refresh
│       └── utils/
│           └── extract-value.ts     # JSON pointer utilities
├── playground/                       # Test application
└── dist/                            # Built module

License

MIT License

Copyright (c) 2024 DommiDev10

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.