Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

A Simple Nuxt authentication module with JWT and social login support for Nuxt Apps with separate backend

License

Notifications You must be signed in to change notification settings

rymcu/nuxt-auth

Open more actions menu
 
 

Repository files navigation

Simple Nuxt Auth

A simple and flexible authentication module for Nuxt 3 with JWT and social login support. This Auth Package only works for a Nuxt app that has a separate backend that handles authenthication

Features

  • JWT Authentication: Secure token-based authentication
  • Social Login: Support for Google OAuth with flexible callback handling
  • Cookie Management: Secure HTTP-only cookies for token storage
  • Token Refresh: Automatic token refresh functionality
  • Route Protection: Built-in middleware for protecting routes
  • Flexible Configuration: Easy to configure and customize
  • SSR Support: Works with both client-side and server-side rendering
  • TypeScript: Full TypeScript support
  • OAuth Flows: Both client-side and server-side OAuth callback handling
  • Auto-imports: Composables and utilities are auto-imported

Quick Setup

  1. Add @andychukse/nuxt-auth dependency to your project
# Using pnpm
pnpm add @andychukse/nuxt-auth

# Using yarn
yarn add @andychukse/nuxt-auth

# Using npm
npm install @andychukse/nuxt-auth
  1. Add @andychukse/nuxt-auth to the modules section of nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@andychukse/nuxt-auth'
  ]
})
  1. Configure the module in your nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@andychukse/nuxt-auth'],
  
  // Configure the auth module
  auth: {
    isEnabled: true,
    baseUrl: '/api/auth',
    callback: '/',
    endpoints: {
      signIn: { path: '/login', method: 'post' },
      signOut: { path: '/logout', method: 'post' },
      signUp: { path: '/register', method: 'post' },
      getSession: { path: '/session', method: 'get' },
      refresh: { path: '/refresh', method: 'post' },
      google: { path: '/google', method: 'post' },
    },
    token: {
      tokenPointer: '/access_token',
      refreshTokenPointer: '/refresh_token',
      type: 'Bearer',
      cookieName: 'auth.token',
      headerName: 'Authorization',
      maxAgeInSeconds: 86400, // 1 day
      sameSiteAttribute: 'lax',
      cookieDomain: '',
      secureCookieAttribute: false,
      httpOnlyCookieAttribute: true,
      refresh: {
        refreshOnlyToken: true,
        cookieName: 'auth.refresh',
        maxAgeInSeconds: 7776000, // 90 days
        requestTokenPointer: '/refresh_token',
        sameSiteAttribute: 'lax',            
        secureCookieAttribute: false,        
        httpOnlyCookieAttribute: true, 
      },
    },
    social: {
      google: {
        clientId: 'your-google-client-id.googleusercontent.com',
        redirectUri: 'http://localhost:3000/auth/google/callback',
        scopes: 'openid profile email'
      }
    },
  },
})

Usage

Basic Authentication Flow

<template>
  <div>
    <div v-if="status === 'loading'">
      <p>Loading...</p>
    </div>
    <div v-else-if="status === 'authenticated'">
      <h1>Welcome {{ data?.name || data?.email }}!</h1>
      <p>Status: {{ status }}</p>
      <button @click="handleSignOut" class="btn-logout">Sign Out</button>
    </div>
    <div v-else>
      <h1>Please sign in</h1>
      <button @click="showLogin = true" class="btn-login">Sign In</button>
      <button @click="showRegister = true" class="btn-register">Register</button>
    </div>
  </div>
</template>

<script setup>
const { status, data, signOut } = useAuth()
const showLogin = ref(false)
const showRegister = ref(false)

const handleSignOut = async () => {
  try {
    await signOut()
    // User will be redirected automatically
  } catch (error) {
    console.error('Sign out failed:', error)
  }
}
</script>

Sign In Form

<template>
  <div class="auth-form">
    <h2>Sign In</h2>
    <form @submit.prevent="handleSignIn">
      <div class="form-group">
        <label for="email">Email:</label>
        <input 
          id="email"
          v-model="form.email" 
          type="email" 
          required 
          placeholder="Enter your email"
        />
      </div>
      
      <div class="form-group">
        <label for="password">Password:</label>
        <input 
          id="password"
          v-model="form.password" 
          type="password" 
          required 
          placeholder="Enter your password"
        />
      </div>
      
      <button type="submit" :disabled="loading" class="btn-submit">
        {{ loading ? 'Signing in...' : 'Sign In' }}
      </button>
      
      <p v-if="error" class="error">{{ error }}</p>
    </form>
    
    <!-- Social Login -->
    <div class="social-login">
      <button @click="handleGoogleSignIn" class="btn-google">
        Sign in with Google
      </button>
    </div>
  </div>
</template>

<script setup>
const { signIn, loading } = useAuth()

const form = reactive({
  email: '',
  password: ''
})

const error = ref('')

const handleSignIn = async () => {
  try {
    error.value = ''
    await signIn(form, { 
      callbackUrl: '/dashboard' // Optional: redirect after login
    })
    // User will be redirected automatically
  } catch (err) {
    error.value = err.message || 'Sign in failed'
  }
}

const handleGoogleSignIn = async () => {
  try {
    // Method 1: Direct OAuth with built-in flow
    await initiateSocialLogin('google', {
      callbackUrl: '/dashboard'
    })
    // This will redirect to Google OAuth
  } catch (err) {
    error.value = err.message || 'Google sign in failed'
  }
}
</script>

Register Form

<template>
  <div class="auth-form">
    <h2>Create Account</h2>
    <form @submit.prevent="handleRegister">
      <div class="form-group">
        <label for="name">Full Name:</label>
        <input 
          id="name"
          v-model="form.name" 
          type="text" 
          required 
          placeholder="Enter your full name"
        />
      </div>
      
      <div class="form-group">
        <label for="email">Email:</label>
        <input 
          id="email"
          v-model="form.email" 
          type="email" 
          required 
          placeholder="Enter your email"
        />
      </div>
      
      <div class="form-group">
        <label for="password">Password:</label>
        <input 
          id="password"
          v-model="form.password" 
          type="password" 
          required 
          placeholder="Choose a password"
          minlength="8"
        />
      </div>
      
      <div class="form-group">
        <label for="confirmPassword">Confirm Password:</label>
        <input 
          id="confirmPassword"
          v-model="form.confirmPassword" 
          type="password" 
          required 
          placeholder="Confirm your password"
        />
      </div>
      
      <button type="submit" :disabled="loading || !isFormValid" class="btn-submit">
        {{ loading ? 'Creating account...' : 'Create Account' }}
      </button>
      
      <p v-if="error" class="error">{{ error }}</p>
    </form>
  </div>
</template>

<script setup>
const { signUp, loading } = useAuth()

const form = reactive({
  name: '',
  email: '',
  password: '',
  confirmPassword: ''
})

const error = ref('')

const isFormValid = computed(() => {
  return form.password === form.confirmPassword && 
         form.password.length >= 8
})

const handleRegister = async () => {
  try {
    error.value = ''
    
    if (!isFormValid.value) {
      error.value = 'Please check your password'
      return
    }
    
    await signUp({
      name: form.name,
      email: form.email,
      password: form.password
    }, {
      callbackUrl: '/welcome' // Optional: redirect after registration
    })
    // User will be redirected automatically
  } catch (err) {
    error.value = err.message || 'Registration failed'
  }
}
</script>

Advanced Google OAuth Integration

The module provides two ways to handle Google OAuth:

1. Client-Side Callback (Recommended for SPAs)

<template>
  <div>
    <button @click="signInWithGoogle" class="google-btn">
      <svg><!-- Google icon --></svg>
      Continue with Google
    </button>
  </div>
</template>

<script setup>
const { initiateSocialLogin } = useAuth()

const signInWithGoogle = async () => {
  try {
    // This will redirect to Google OAuth and handle the callback client-side
    await initiateSocialLogin('google', {
      clientId: 'your-google-client-id',
      redirectUri: 'http://localhost:3000/auth/google/callback',
      scopes: 'openid profile email',
      callbackUrl: '/dashboard' // Where to go after successful auth
    })
  } catch (error) {
    console.error('Google OAuth failed:', error)
  }
}
</script>

Create the callback page at /pages/auth/google/callback.vue:

<template>
  <div class="callback-handler">
    <div v-if="loading">
      <p>Processing Google authentication...</p>
    </div>
    <div v-else-if="error">
      <p>Authentication failed: {{ error }}</p>
      <button @click="$router.push('/login')">Back to Login</button>
    </div>
  </div>
</template>

<script setup>
const { handleSocialCallback } = useAuth()
const route = useRoute()

const loading = ref(true)
const error = ref('')

onMounted(async () => {
  try {
    const { code, state } = route.query
    
    if (code) {
      await handleSocialCallback('google', code, state)
      // User will be redirected to callbackUrl automatically
    } else {
      throw new Error('No authorization code received')
    }
  } catch (err) {
    error.value = err.message
  } finally {
    loading.value = false
  }
})
</script>

2. Server-Side Callback (Recommended for SSR)

<script setup>
const { initiateSocialLogin } = useAuth()

const signInWithGoogleSSR = async () => {
  try {
    // This will use server-side callback handling
    await initiateSocialLogin('google', {
      callbackUrl: '/dashboard',
      callbackPage: 'auth/custom-handler' // Server will redirect here with OAuth params
    })
  } catch (error) {
    console.error('Google OAuth failed:', error)
  }
}
</script>

Route Protection with Middleware

Protect routes by adding the auth middleware:

<script setup>
definePageMeta({
  middleware: 'auth'
})
</script>

<template>
  <div>
    <h1>Protected Page</h1>
    <p>This page is only accessible to authenticated users.</p>
  </div>
</template>

For guest-only pages (login, register), use the guest middleware:

<script setup>
definePageMeta({
  middleware: 'guest'
})
</script>

<template>
  <div>
    <h1>Login Page</h1>
    <!-- Login form here -->
  </div>
</template>

Social Authentication

For Google authentication:

<template>
  <button @click="signInWithGoogle">Sign in with Google</button>
</template>

<script setup>
const { signInWithSocial } = useAuth()

const signInWithGoogle = async () => {
  try {
    await signInWithSocial('google', { 
      code: 'google-auth-code',
      state: 'random-state' 
    })
  } catch (error) {
    console.error('Google sign-in failed:', error)
  }
}
</script>

API Reference

useAuth()

The main composable that provides authentication functionality.

Returns

  • status: Reactive authentication status ('loading' | 'authenticated' | 'unauthenticated')
  • data: Reactive user data object
  • token: Reactive access token
  • refreshToken: Reactive refresh token
  • loading: Reactive loading state
  • signUp(userData, options?): Function to register a new user
  • signIn(loginData, options?): Function to sign in a user
  • signOut(): Function to sign out the current user
  • getSession(): Function to fetch the current user session
  • signInWithSocial(provider, params, options?): Function for social authentication
  • initiateSocialLogin(provider, options?): Function to start OAuth flow
  • handleSocialCallback(provider, code, state?): Function to handle OAuth callback
  • refreshAuthToken(): Function to refresh the access token
  • clearTokens(): Function to clear all tokens

Function Signatures

// Sign up a new user
signUp(userData: any, options?: { callbackUrl?: string }): Promise<void>

// Sign in an existing user
signIn(loginData: any, options?: { callbackUrl?: string }): Promise<void>

// Sign in with social provider (after getting OAuth code)
signInWithSocial(provider: string, params: any, options?: { callbackUrl?: string }): Promise<void>

// Initiate OAuth flow (redirects to provider)
initiateSocialLogin(provider: string, options?: {
  callbackUrl?: string
  clientId?: string
  redirectUri?: string
  scopes?: string[]
  state?: string
  callbackPage?: string // For server-side callback
}): Promise<void>

// Handle OAuth callback (client-side)
handleSocialCallback(provider: string, code: string, state?: string): Promise<void>

Utility Functions

authResponseError(response)

Extracts error messages from API responses.

Configuration Options

Option Type Default Description
isEnabled boolean true Enable/disable the auth module
baseUrl string '/api/auth' API base URL for authentication endpoints
callback string '/' Default redirect after successful authentication
endpoints object See below API endpoint configuration
token object See below Token configuration
social object See below Social authentication configuration

Endpoint Configuration

endpoints: {
  signIn: { path: '/login', method: 'post' },
  signOut: { path: '/logout', method: 'post' },
  signUp: { path: '/register', method: 'post' },
  getSession: { path: '/session', method: 'get' },
  refresh: { path: '/refresh', method: 'post' },
  google: { path: '/google', method: 'post' },
}

Token Configuration

token: {
  tokenPointer: '/access_token',        // Path to access token in API response
  refreshTokenPointer: '/refresh_token', // Path to refresh token in API response
  type: 'Bearer',                       // Token type for Authorization header
  cookieName: 'auth.token',            // Cookie name for access token
  headerName: 'Authorization',          // Header name for API requests
  maxAgeInSeconds: 86400,              // Token expiration (1 day)
  sameSiteAttribute: 'lax',            // Cookie SameSite attribute
  cookieDomain: '',                    // Cookie domain (empty = current domain)
  secureCookieAttribute: true,        // Use HTTPS only (true in production)
  httpOnlyCookieAttribute: true,      // HTTP-only cookies (recommended: true)
  refresh: {
    refreshOnlyToken: true,            // Only send refresh token (not access token)
    cookieName: 'auth.refresh',        // Cookie name for refresh token
    maxAgeInSeconds: 7776000,          // Refresh token expiration (90 days)
    requestTokenPointer: '/refresh_token', // Path to refresh token in request
    sameSiteAttribute: 'lax',            // Cookie SameSite (strict in production)
    secureCookieAttribute: true,        // Use HTTPS only (true in production)
    httpOnlyCookieAttribute: true,      // HTTP-only cookies (recommended: true)
  },
}

Social Authentication Configuration

social: {
  google: {
    clientId: 'your-google-client-id.googleusercontent.com',
    redirectUri: 'http://localhost:3000/auth/google/callback',
    scopes: 'openid profile email'
  }
}

Server Routes

The module automatically adds these server routes for social authentication:

  • /api/auth/google/callback - Google OAuth callback

Security Considerations

  • Always use HTTPS in production (secureCookieAttribute: true)
  • Consider using HTTP-only cookies (httpOnlyCookieAttribute: true)
  • Set appropriate cookie SameSite attributes
  • Validate tokens on the server side
  • Use short token expiration times with refresh tokens

Development

# Install dependencies
npm install

# Generate type stubs
npm run dev:prepare

# Develop with the playground
npm run dev

# Build the module
npm run prepack

# Run tests
npm run test

Contributing

Here’s an improved and up-to-date section for your README.md, including install instructions, sample usage for sign in, register, sign out, Google login, a modern config example, a development section, and a contribution guide. I’ll insert this content in the appropriate places and update the development/contribution sections at the end.


## Installation

```bash
npm install @andychukse/nuxt-auth
# or
yarn add @andychukse/nuxt-auth
# or
pnpm add @andychukse/nuxt-auth

Module Setup

Add the module and configure it in your nuxt.config.ts:

export default defineNuxtConfig({
  modules: ['@andychukse/nuxt-auth'],
  auth: {
    isEnabled: true,
    baseUrl: 'https://api_url/auth',
    callback: '/',
    endpoints: {
      signIn: { path: '/login', method: 'post' },
      signOut: { path: '/logout', method: 'post' },
      signUp: { path: '/register', method: 'post' },
      getSession: { path: '/session', method: 'get' },
      refresh: { path: '/refresh', method: 'post' },
      google: { path: '/google', method: 'post' },
    },
    token: {
      tokenPointer: '/access_token',
      refreshTokenPointer: '/refresh_token',
      type: 'Bearer',
      cookieName: 'auth.token',
      headerName: 'Authorization',
      maxAgeInSeconds: 86400,
      sameSiteAttribute: 'lax',
      cookieDomain: '',
      secureCookieAttribute: false,
      httpOnlyCookieAttribute: true,
      refresh: {
        refreshOnlyToken: true,
        cookieName: 'auth.refresh',
        maxAgeInSeconds: 7776000,
        requestTokenPointer: '/refresh_token',
        sameSiteAttribute: 'lax',            
        secureCookieAttribute: false,   
        httpOnlyCookieAttribute: true, 
      },
    },
    social: {
      google: {
        clientId: 'your-google-client-id.googleusercontent.com',
        redirectUri: 'http://localhost:3000/auth/google/callback',
        scopes: 'openid profile email'
      }
    },
  },
})

Usage Examples

Sign In

<script setup>
const { signIn, loading } = useAuth()
const form = reactive({ email: '', password: '' })
const error = ref('')

const handleSignIn = async () => {
  try {
    error.value = ''
    await signIn(form, { callbackUrl: '/dashboard' })
  } catch (err) {
    error.value = err.message || 'Sign in failed'
  }
}
</script>

Register

<script setup>
const { signUp, loading } = useAuth()
const form = reactive({ name: '', email: '', password: '', confirmPassword: '' })
const error = ref('')

const handleRegister = async () => {
  try {
    error.value = ''
    if (form.password !== form.confirmPassword) {
      error.value = 'Passwords do not match'
      return
    }
    await signUp({
      name: form.name,
      email: form.email,
      password: form.password
    }, { callbackUrl: '/welcome' })
  } catch (err) {
    error.value = err.message || 'Registration failed'
  }
}
</script>

Sign Out

<script setup>
const { signOut } = useAuth()
const handleSignOut = async () => {
  try {
    await signOut()
  } catch (error) {
    console.error('Sign out failed:', error)
  }
}
</script>

Sign In with Google

<script setup>
const { initiateSocialLogin } = useAuth()
const handleGoogleSignIn = async () => {
  try {
    await initiateSocialLogin('google', {
      callbackUrl: '/dashboard'
    })
  } catch (err) {
    console.error('Google sign in failed:', err)
  }
}
</script>

Implementation Social Auth (Google) Server Side (SSR):

<template>
<button
     :disabled="loading"
     class="google-btn"
     @click="handleGoogleSignIn"
   >
   <span v-if="loading">Signing in...</span>
     <span v-else>
       Sign in with Google
     </span>
</button>
</template>
<script setup>
   const { initiateSocialLogin, handleSocialCallback } = useAuth()

   const loading = ref(false)
   const error = ref('')

   const handleGoogleSignIn = async () => {
       loading.value = true
       error.value = ''

       //use current page as callback page
       const redirectPath =  path.replace(/^\/|\/$/g, '') as string

       await initiateSocialLogin('google', {
           callbackUrl: '/register',
           callbackPage: redirectPath
       })
   }

   onMounted(() => {
   // Check if this is a callback from Google OAuth
   const code = route.query.code as string
   const state = route.query.state

   handleSocialCallback('google', code, state)

   })
</script>

Implementation Social Auth (Google) Client Side:

  1. Set Google redirect URI to your Vue page:

    https://yourapp.com/auth/callback/google
    
  2. Create callback page (/pages/auth/callback/google.vue):

    <script setup>
    const route = useRoute()
    const { handleSocialCallback } = useAuth()
    
    onMounted(async () => {
      const code = route.query.code
      const state = route.query.state
      await handleSocialCallback('google', code, state)
    })
    </script>
  3. Initiate OAuth in your login or register component:

    const { initiateSocialLogin } = useAuth()
    
    const handleGoogleLogin = async () => {
      await initiateSocialLogin('google', {
        callbackUrl: '/dashboard'
      })
    }

Development

# Install dependencies
npm install

# Develop with the playground app
npm run dev

# Build the module for production
npm run prepack

# Run tests
npm run test

Contributing

  1. Fork this repo and create your feature branch (git checkout -b feature/my-feature)
  2. Commit your changes (git commit -am 'Add new feature')
  3. Push to the branch (git push origin feature/my-feature)
  4. Open a pull request
  • Please write clear commit messages and add tests for new features.
  • For major changes, open an issue first to discuss what you’d like to change.

License

MIT License

About

A Simple Nuxt authentication module with JWT and social login support for Nuxt Apps with separate backend

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 56.0%
  • Vue 43.2%
  • JavaScript 0.8%
Morty Proxy This is a proxified and sanitized view of the page, visit original site.