Authentication
Learn how to implement authentication in your Nuxt application using Nuxt Users.
Overview
The module provides a complete authentication system with:
- Secure login with bcrypt password hashing
- Token-based authentication (inspired by Laravel Sanctum)
- HTTP-only cookies for security
- Automatic token management
Whitelisting Routes
By default, all pages (except /login) require authentication. You can whitelist routes that should be accessible without authentication using the auth.whitelist option in your nuxt.config.ts.
If you want to add other pages that can be accessed without authentication, like a /register page, you can do so like this:
export default defineNuxtConfig({
modules: ['nuxt-users'],
nuxtUsers: {
auth: {
whitelist: ['/register'], // /confirm-email is automatically added when /register is present
},
},
})Note: When you add /register to the whitelist, the module automatically adds /confirm-email as well, since users need to access email confirmation links without authentication.
Authentication Flow
Upon successful login:
- User submits credentials - Email and password are sent to the server
- Password verification - The server verifies the password securely
- Token generation - A secure authentication token is created
- Cookie setting - An HTTP-only cookie is set in the browser
- Response - User data is returned to your application
Login API
Endpoint
POST /api/nuxt-users/session
Request Body
{
"email": "[email protected]",
"password": "password123"
}Response
{
"user": {
"id": 1,
"email": "[email protected]",
"name": "John Doe",
"created_at": "2024-01-01T00:00:00.000Z",
"updated_at": "2024-01-01T00:00:00.000Z"
}
}Error Responses
// 400 Bad Request
{
"statusMessage": "Email and password are required"
}
// 401 Unauthorized
{
"statusMessage": "Invalid email or password"
}Using the NUsersLoginForm Component
The module provides a ready-to-use NUsersLoginForm component:
<template>
<NUsersLoginForm
@success="handleSuccess"
@error="handleError"
/>
</template>
<script setup>
const handleSuccess = (user, rememberMe) => {
console.log('Login successful:', user, 'Remember me:', rememberMe)
// The login composable will automatically handle the rememberMe setting
// Redirect or update UI
await navigateTo('/dashboard')
}
const handleError = (error) => {
console.log('Login error:', error)
// Show error message to user
}
</script>Custom Authentication
Manual Login
You can implement custom login logic:
<template>
<form @submit.prevent="handleLogin">
<input v-model="email" type="email" placeholder="Email" required />
<input v-model="password" type="password" placeholder="Password" required />
<button type="submit" :disabled="loading">
{{ loading ? 'Logging in...' : 'Login' }}
</button>
</form>
</template>
<script setup>
const email = ref('')
const password = ref('')
const loading = ref(false)
const handleLogin = async () => {
loading.value = true
try {
const response = await $fetch('/api/nuxt-users/session', {
method: 'POST',
body: {
email: email.value,
password: password.value
}
})
// Handle successful login
console.log('User:', response.user)
await navigateTo('/dashboard')
} catch (error) {
// Handle error
console.error('Login failed:', error)
// Show error message to user
} finally {
loading.value = false
}
}
</script>For detailed usage of the useAuthentication composable, refer to the Composables documentation.
User Registration
The module provides a complete user registration system with email confirmation to ensure valid email addresses and prevent spam accounts.
Registration Flow
- User submits registration form - Email, name, and password are validated
- Password strength validation - Ensures password meets security requirements
- User account created - Account is created in inactive state
- Confirmation email sent - User receives email with secure confirmation link
- User clicks confirmation link - Account is activated and ready for login
Using the NUsersRegisterForm Component
The module provides a ready-to-use NUsersRegisterForm component:
<template>
<NUsersRegisterForm
@success="handleRegistrationSuccess"
@error="handleRegistrationError"
/>
</template>
<script setup>
const handleRegistrationSuccess = (data) => {
console.log('Registration successful:', data.user)
console.log('Message:', data.message)
// Show success message to user
}
const handleRegistrationError = (error) => {
console.log('Registration failed:', error)
// Show error message to user
}
</script>Configuration Requirements
To enable registration, you need to:
- Whitelist the registration route in your
nuxt.config.ts - Configure email settings for sending confirmation emails
export default defineNuxtConfig({
modules: ['nuxt-users'],
nuxtUsers: {
auth: {
whitelist: ['/register'], // Also auto-whitelists /confirm-email
},
// Email configuration for confirmation emails
mailer: {
host: 'smtp.your-provider.com',
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: '[email protected]',
pass: 'your-password'
},
defaults: {
from: '"Your App" <[email protected]>'
}
},
// Configure password requirements
passwordValidation: {
minLength: 8,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSpecialChars: true,
preventCommonPasswords: true
}
}
})Registration API
Endpoint: POST /api/nuxt-users/register
Request Body:
{
"email": "[email protected]",
"name": "John Doe",
"password": "securePassword123!"
}Response:
{
"user": {
"id": 1,
"email": "[email protected]",
"name": "John Doe",
"role": "user",
"created_at": "2024-01-01T00:00:00.000Z",
"updated_at": "2024-01-01T00:00:00.000Z"
},
"message": "Registration successful! Please check your email to confirm your account."
}Email Confirmation
Endpoint: GET /api/nuxt-users/confirm-email
Query Parameters:
token: Email confirmation token from the registration emailemail: User's email address
Example:
GET /api/nuxt-users/confirm-email?token=abc123def456&[email protected]Security Features
- Email verification required: Prevents fake email registrations
- Inactive accounts: New accounts remain inactive until email confirmation
- Secure tokens: Confirmation tokens are hashed and expire after 24 hours
- Password strength validation: Enforces strong password requirements
- Duplicate prevention: Prevents registration with existing email addresses
Logout
The module provides a complete logout system that securely removes authentication tokens and clears user sessions.
Using the NUsersLogoutLink Component
The module provides a ready-to-use NUsersLogoutLink component:
<template>
<NUsersLogoutLink
@success="handleSuccess"
@error="handleError"
/>
</template>
<script setup>
const handleSuccess = () => {
console.log('Logout successful')
// Handle successful logout
await navigateTo('/login')
}
const handleError = (error) => {
console.log('Logout error:', error)
// Show error message
}
</script>Customizing the NUsersLogoutLink
You can customize the appearance and behavior:
<NUsersLogoutLink
link-text="Sign Out"
redirect-to="/home"
confirm-message="Are you sure you want to sign out?"
class="custom-logout-link"
/>Manual Logout
For manual logout using the useAuthentication composable, refer to the Composables documentation.
Direct API Call
You can also call the logout API directly:
<script setup>
const logout = async () => {
try {
await $fetch('/api/nuxt-users/session', { method: 'DELETE' })
console.log('Logged out successfully')
await navigateTo('/login')
} catch (error) {
console.error('Logout failed:', error)
}
}
</script>Token Management
Configuring Token Expiration
Set token expiration time in your nuxt.config.ts:
export default defineNuxtConfig({
modules: ['nuxt-users'],
nuxtUsers: {
auth: {
tokenExpiration: 1440, // 24 hours in minutes (for regular sessions)
rememberMeExpiration: 30, // 30 days (for "remember me" sessions)
}
}
})Remember Me Functionality
The module provides secure "remember me" functionality that:
Regular login (remember me unchecked):
- Sets session-only HTTP cookies that expire when browser closes
- Stores user data in
sessionStorage(cleared when tab closes) - Uses
tokenExpirationsetting (default: 24 hours)
Remember me login (remember me checked):
- Sets persistent HTTP cookies with longer expiration
- Stores user data in
localStoragefor persistence across browser sessions - Uses
rememberMeExpirationsetting (default: 30 days)
The module automatically handles:
- Token expiration validation
- Automatic token cleanup
- Token refresh on activity
- Hard reload persistence: Users remain logged in after hard refresh (Ctrl+F5)
Security Features
Built-in Security
The authentication system includes several security features:
- Cryptographically secure tokens: Tokens are generated using secure random methods
- HTTP-only cookies: Prevents XSS attacks by making tokens inaccessible to JavaScript
- Automatic expiration: Tokens expire based on your configuration (default: 24 hours)
- Expired token cleanup: Automatic cleanup of expired tokens
- Token revocation: Ability to revoke all tokens for a user
Rate Limiting (Recommended)
For production deployments, we strongly recommend using nuxt-api-shield to protect authentication endpoints from brute force attacks:
# Install the rate limiting module
npx nuxi module add nuxt-api-shieldConfigure rate limiting in your nuxt.config.ts:
export default defineNuxtConfig({
modules: ['nuxt-users', 'nuxt-api-shield'],
nuxtUsers: {
// ... your nuxt-users config
},
apiShield: {
maxRequests: 5, // 5 login attempts per duration
duration: 60000, // 1 minute window
banDuration: 300000, // 5 minute ban for violators
delay: 1000, // 1 second delay on banned IPs
routes: [
'/api/nuxt-users/session', // Protect login endpoint
'/api/nuxt-users/password/forgot', // Protect password reset requests
'/api/nuxt-users/password/reset' // Protect password reset completion
],
log: true // Enable logging for monitoring
}
})This configuration provides:
- Brute force protection: Limits login attempts per IP
- Automatic IP banning: Temporarily blocks malicious IPs
- Password reset protection: Prevents abuse of reset functionality
- Configurable thresholds: Adjustable for your security needs
- Monitoring: Logs for security analysis
Advanced Rate Limiting
For different security levels on different endpoints:
apiShield: {
routes: {
'/api/nuxt-users/session': {
maxRequests: 5, // Stricter limit for login
duration: 60000, // 1 minute
banDuration: 600000 // 10 minute ban
},
'/api/nuxt-users/password/forgot': {
maxRequests: 3, // Very strict for password reset
duration: 300000, // 5 minute window
banDuration: 1800000 // 30 minute ban
}
}
}Server-Side Authentication
Using getCurrentUser() in API Routes
For server-side authentication in your API routes, use the getCurrentUser() function from the server-side useServerAuth() composable. This is essential for protecting API endpoints and accessing user data in server contexts.
// server/api/profile.get.ts
import { useServerAuth } from '#nuxt-users/server'
export default defineEventHandler(async (event) => {
const { getCurrentUser } = useServerAuth()
const user = await getCurrentUser(event)
if (!user) {
throw createError({
statusCode: 401,
statusMessage: 'Authentication required'
})
}
return {
profile: {
id: user.id,
name: user.name,
email: user.email,
role: user.role,
lastLogin: user.last_login_at
}
}
})Role-Based API Protection
// server/api/admin/users.get.ts
import { useServerAuth } from '#nuxt-users/server'
export default defineEventHandler(async (event) => {
const { getCurrentUser } = useServerAuth()
const user = await getCurrentUser(event)
// Check authentication
if (!user) {
throw createError({ statusCode: 401, statusMessage: 'Authentication required' })
}
// Check authorization
if (user.role !== 'admin') {
throw createError({ statusCode: 403, statusMessage: 'Admin access required' })
}
// Admin-only logic here
const allUsers = await fetchAllUsers()
return { users: allUsers }
})User-Specific Data Access
// server/api/posts/my-posts.get.ts
import { useServerAuth } from '#nuxt-users/server'
export default defineEventHandler(async (event) => {
const { getCurrentUser } = useServerAuth()
const user = await getCurrentUser(event)
if (!user) {
throw createError({ statusCode: 401, statusMessage: 'Authentication required' })
}
// Fetch posts belonging to the current user
const userPosts = await fetchPostsByUserId(user.id)
return { posts: userPosts }
})Optional Authentication
Some endpoints may provide different data based on authentication status:
// server/api/posts/public.get.ts
import { useServerAuth } from '#nuxt-users/server'
export default defineEventHandler(async (event) => {
const { getCurrentUser } = useServerAuth()
const user = await getCurrentUser(event) // Returns null if not authenticated
const posts = await fetchPublicPosts()
// Add extra data for authenticated users
if (user) {
const postsWithUserData = posts.map(post => ({
...post,
isLiked: await checkIfUserLikedPost(user.id, post.id),
canEdit: post.author_id === user.id || user.role === 'admin'
}))
return { posts: postsWithUserData }
}
// Return basic data for non-authenticated users
return { posts }
})Database Operations with User Context
// server/api/comments.post.ts
import { useServerAuth } from '#nuxt-users/server'
import { readBody } from 'h3'
export default defineEventHandler(async (event) => {
const { getCurrentUser } = useServerAuth()
const user = await getCurrentUser(event)
if (!user) {
throw createError({ statusCode: 401, statusMessage: 'Authentication required' })
}
const { postId, content } = await readBody(event)
// Create comment with authenticated user's ID
const newComment = await createComment({
post_id: postId,
author_id: user.id, // Use authenticated user's ID
content,
created_at: new Date()
})
return { comment: newComment }
})Middleware Pattern
Create reusable authentication middleware:
// server/utils/authMiddleware.ts
import { useServerAuth } from '#nuxt-users/server'
import type { UserWithoutPassword } from 'nuxt-users/utils'
export const requireAuth = async (event: any): Promise<UserWithoutPassword> => {
const { getCurrentUser } = useServerAuth()
const user = await getCurrentUser(event)
if (!user) {
throw createError({
statusCode: 401,
statusMessage: 'Authentication required'
})
}
return user
}
export const requireRole = async (event: any, requiredRole: string): Promise<UserWithoutPassword> => {
const user = await requireAuth(event)
if (user.role !== requiredRole) {
throw createError({
statusCode: 403,
statusMessage: `${requiredRole} access required`
})
}
return user
}Then use the middleware in your API routes:
// server/api/admin/dashboard.get.ts
import { requireRole } from '~/server/utils/authMiddleware'
export default defineEventHandler(async (event) => {
const adminUser = await requireRole(event, 'admin')
// Admin-only logic here
return {
message: `Welcome admin ${adminUser.name}!`,
stats: await getAdminStats()
}
})Checking Authentication Status
For checking authentication status using the useAuthentication composable, refer to the Composables documentation.
For accessing the current user in client-side components, see the getCurrentUser() documentation.
Error Handling
For error handling with the useAuthentication composable, refer to the Composables documentation.
Google OAuth Authentication
The module provides built-in support for Google OAuth authentication, allowing users to register and login using their Google accounts.
Google Cloud Setup
Before implementing Google OAuth, you need to set up a project in Google Cloud Console:
Create a Google Cloud Project:
- Go to Google Cloud Console
- Create a new project or select an existing one
Enable Google+ API:
- Navigate to "APIs & Services" > "Library"
- Search for "Google+ API" or "People API"
- Click "Enable"
Create OAuth 2.0 Credentials:
- Go to "APIs & Services" > "Credentials"
- Click "Create Credentials" > "OAuth 2.0 Client ID"
- Choose "Web application"
- Add your callback URL:
https://yourdomain.com/api/nuxt-users/auth/google/callback - Save your Client ID and Client Secret
Configuration
Add Google OAuth configuration to your nuxt.config.ts:
export default defineNuxtConfig({
modules: ['nuxt-users'],
nuxtUsers: {
auth: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
// Optional: customize URLs and scopes
callbackUrl: '/api/nuxt-users/auth/google/callback',
successRedirect: '/dashboard',
errorRedirect: '/login?error=oauth_failed',
scopes: ['openid', 'profile', 'email'],
// Control automatic user registration (default: false)
allowAutoRegistration: false
}
}
}
})Environment Variables
Add your Google OAuth credentials to your environment variables:
# .env
GOOGLE_CLIENT_ID=your_google_client_id_here
GOOGLE_CLIENT_SECRET=your_google_client_secret_hereDatabase Migration
For existing databases, run the migration to add Google OAuth fields:
# Add google_id and profile_picture columns to users table
npx nuxt-users add-google-oauth-fieldsFor new installations, the fields are automatically included when creating the users table.
Using the Google Login Button
The module provides a ready-to-use NUsersGoogleLoginButton component:
<template>
<div class="login-page">
<h1>Sign In</h1>
<!-- Traditional login form -->
<NUsersLoginForm @success="handleLoginSuccess" />
<!-- Divider -->
<div class="divider">
<span>or</span>
</div>
<!-- Google OAuth button -->
<NUsersGoogleLoginButton
@click="handleGoogleLogin"
button-text="Sign in with Google"
class="google-login-btn"
/>
</div>
</template>
<script setup>
const handleLoginSuccess = (user) => {
console.log('Login successful:', user)
await navigateTo('/dashboard')
}
const handleGoogleLogin = () => {
console.log('Starting Google OAuth flow')
}
</script>Component Props
The NUsersGoogleLoginButton component accepts these props:
buttonText(string): Button text (default: "Continue with Google")showLogo(boolean): Show Google logo (default: true)redirectEndpoint(string): Custom OAuth redirect endpointclass(string): Custom CSS class
OAuth Flow
The Google OAuth authentication flow works as follows:
- User clicks Google login button → Redirects to
/api/nuxt-users/auth/google/redirect - Redirect to Google → User is sent to Google's OAuth consent screen
- User grants permission → Google redirects back to
/api/nuxt-users/auth/google/callback - Process OAuth response → Module exchanges code for user info and verifies email
- Find or create user → Module checks if user exists:
- Existing Google user → Update profile picture if changed
- Existing user by email → Link Google account automatically
- New user +
allowAutoRegistration: true→ Create new account - New user +
allowAutoRegistration: false→ Reject with error
- Set authentication cookie → User is logged in with persistent session (30 days by default)
- Redirect → User is redirected to
successRedirectorerrorRedirectbased on outcome
User Account Linking & Auto-Registration
The module intelligently handles different user scenarios based on the allowAutoRegistration configuration:
Scenario 1: Existing User with Google Account
When a user who has previously linked their Google account logs in:
- ✅ User is authenticated immediately
- ✅ Profile picture is automatically updated if changed
- ✅ User is redirected to
successRedirect
Scenario 2: Existing User by Email (Auto-Linking)
When a user with an existing account (created via traditional registration) logs in with Google for the first time:
- ✅ Google account is automatically linked to the existing user account
- ✅ User's
google_idis saved for future logins - ✅ Profile picture is added/updated
- ✅ User can now log in with either password or Google
Scenario 3: New User (Controlled by allowAutoRegistration)
When allowAutoRegistration: false (default - recommended for production):
- ❌ New users cannot self-register via Google
- ❌ User is redirected to
errorRedirectwitherror=user_not_registered - ✅ More secure for invite-only or controlled access systems
- ✅ Prevents unauthorized account creation
// Secure configuration - only existing users can log in with Google
auth: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
allowAutoRegistration: false // Default
}
}When allowAutoRegistration: true (for public registration):
- ✅ New users are automatically registered when they log in with Google
- ✅ Account is created with:
- Email and name from Google profile
- Cryptographically secure random password (user won't need it)
- Profile picture from Google
- Default role:
'user' - Account status:
active
- ✅ User is immediately logged in and redirected to
successRedirect
// Open registration - anyone with a Google account can register
auth: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
allowAutoRegistration: true // Allow public registration
}
}Security Considerations
Use allowAutoRegistration: false when:
- You want to control who can access your application
- You have an invite-only system
- You want admins to manually approve users
- You're building an internal/private application
Use allowAutoRegistration: true when:
- You want public registration
- You're building a consumer-facing application
- You trust Google's email verification
- You have additional security measures in place (role-based access, etc.)
Security Features
- Secure password generation: OAuth users get cryptographically secure random passwords
- Email verification: Only verified Google emails are accepted
- Account activation: Inactive accounts are blocked from OAuth login
- Profile picture sync: User profile pictures are automatically updated
- Token management: Uses the same secure token system as password authentication
Error Handling
The OAuth flow handles various error scenarios and redirects to errorRedirect with specific error codes:
oauth_failed: User cancels Google consent or OAuth process failsoauth_not_configured: Missing or invalid Google OAuth configuration (client ID/secret)user_not_registered: New user attempted login whenallowAutoRegistration: false(default)account_inactive: User account exists but is marked as inactiveoauth_failed: Google API failures or token exchange errors
Example error handling in your login page:
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const errorMessage = ref('')
const errorMessages = {
oauth_failed: 'Google authentication failed. Please try again.',
user_not_registered: 'You are not registered. Please sign up first or contact an administrator.',
account_inactive: 'Your account is inactive. Please contact support.',
oauth_not_configured: 'OAuth is not properly configured. Please contact support.'
}
onMounted(() => {
const error = route.query.error
if (error && errorMessages[error]) {
errorMessage.value = errorMessages[error]
}
})
</script>
<template>
<div v-if="errorMessage" class="error-banner">
{{ errorMessage }}
</div>
<NUsersGoogleLoginButton />
</template>Customization
You can customize the OAuth behavior with all available options:
// nuxt.config.ts
export default defineNuxtConfig({
nuxtUsers: {
auth: {
google: {
// Required: OAuth credentials from Google Cloud Console
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
// Control user registration (default: false)
allowAutoRegistration: false,
// Customize redirect URLs
successRedirect: '/welcome', // After successful login (default: '/')
errorRedirect: '/login?error=google_failed', // After failed login
// Request additional permissions (default: ['openid', 'profile', 'email'])
scopes: ['openid', 'profile', 'email', 'https://www.googleapis.com/auth/user.birthday.read'],
// Custom callback URL - must match Google Cloud Console configuration
callbackUrl: '/api/nuxt-users/auth/google/callback'
}
}
}
})Configuration Options Reference
| Option | Type | Default | Description |
|---|---|---|---|
clientId | string | required | Google OAuth client ID from Google Cloud Console |
clientSecret | string | required | Google OAuth client secret from Google Cloud Console |
allowAutoRegistration | boolean | false | Allow new users to auto-register via Google |
successRedirect | string | '/' | Redirect URL after successful authentication |
errorRedirect | string | '/login?error=oauth_failed' | Redirect URL after failed authentication |
scopes | string[] | ['openid', 'profile', 'email'] | Google OAuth scopes to request |
callbackUrl | string | '/api/nuxt-users/auth/google/callback' | OAuth callback URL (must match Google Console) |
Security Best Practices
- Use HTTPS: Always use HTTPS in production to protect credentials in transit
- Strong passwords: Enforce strong password policies for your users
- Rate limiting: Use nuxt-api-shield to protect authentication endpoints
- Token expiration: Set reasonable expiration times (default: 24 hours)
- Secure storage: Never store authentication tokens in localStorage or sessionStorage
- Input validation: Validate all user inputs on both client and server
- Error messages: Don't reveal too much information in error messages
- Monitoring: Monitor authentication attempts and failures
- Regular updates: Keep the module and dependencies updated
- OAuth security: Keep your Google Client Secret secure and never expose it to the client
Next Steps
- Authorization (RBAC) - Learn about Role-Based Access Control
- Password Reset - Add password reset functionality
- Components - Learn about available Vue components
- Configuration - Explore all configuration options
