This library is in early development. Expect breaking changes.
Core Concepts

Route Protection

Learn how to protect your pages and API routes using a layered approach.

Quick Reference

MethodScopeUse Case
Route RulesGlobalProtecting whole sections (e.g., /admin/**).
Page MetaPer-PageSpecific logic for a single page.
MiddlewareClientComplex client-side navigation logic.
Server UtilsAPIProtecting API endpoints.

Matching Logic

Role arrays use OR logic: the user needs any one of the listed values.

// User needs role 'admin' OR 'moderator' (not both)
auth: { user: { role: ['admin', 'moderator'] } }

For AND logic (user needs multiple conditions), use a rule callback:

auth: {
  rule: (session) => session.user.role === 'admin' && session.user.verified
}

1. Route Rules

The most efficient way to protect your app is using routeRules in nuxt.config.ts. This allows you to define access patterns centrally.

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // Authenticated users only
    '/app/**': { auth: 'user' },
    
    // Guests only (e.g. login page)
    '/login': { auth: 'guest' },
    
    // Admin role only
    '/admin/**': { auth: { user: { role: 'admin' } } },
    
    // Admin OR Moderator
    '/staff/**': { 
      auth: { 
        user: { role: ['admin', 'moderator'] } 
      } 
    }
  }
})
These rules are automatically synced to the client-side router, ensuring instant redirects without server roundtrips for navigation.

2. Page Meta

For page-specific control, use definePageMeta within your Vue components. This overrides global routeRules.

pages/dashboard.vue
<script setup>
definePageMeta({
  auth: 'user'
})
</script>

Advanced Options

You can pass an object for granular control:

definePageMeta({
  auth: {
    // Only allow authenticated users
    only: 'user',
    
    // Redirect blocked users to a specific page
    redirectTo: '/subscribe',
    
    // Match specific user properties
    user: {
      verified: true
    }
  }
})

3. Server API Protection

Protecting your API endpoints is critical. Use requireUserSession to enforce authentication on server routes.

server/api/secret.get.ts
export default defineEventHandler(async (event) => {
  // Throws 401 if not logged in
  const { user } = await requireUserSession(event)
  
  return { secret: 'data' }
})

Role-Based Access

You can also pass requirements to requireUserSession:

await requireUserSession(event, {
  // User must match ALL conditions
  user: {
    role: 'admin',
    verified: true,
    // OR logic for array values
    plan: ['pro', 'enterprise']
  }
})
// Custom fields (like 'plan') must be defined in your Better Auth schema
auth: { user: { plan: ['pro', 'enterprise'] } }

Safe Redirects After Login

When preserving the original URL for post-login redirects, validate it to prevent open redirect attacks:

pages/login.vue
const route = useRoute()

function getSafeRedirect() {
  const redirect = route.query.redirect as string
  if (!redirect || !redirect.startsWith('/') || redirect.startsWith('//')) {
    return '/'
  }
  return redirect
}

async function login(email: string, password: string) {
  await signIn.email({
    email,
    password,
    onSuccess: () => navigateTo(getSafeRedirect()),
  })
}
Always validate redirect URLs. Accepting arbitrary URLs allows attackers to redirect users to malicious sites after login.

Advanced API Patterns

Custom Rule Callback

For complex authorization logic:

server/api/admin/report.ts
const session = await requireUserSession(event, {
  user: { emailVerified: true },
  rule: ({ user }) => user.subscriptionActive
})

WebSocket Handlers

server/api/ws.ts
import { defineWebSocketHandler } from 'h3'

export default defineWebSocketHandler({
  open: async (peer) => {
    await requireUserSession(peer.ctx.event, { user: { role: 'member' } })
  },
})

CSRF Protection

Better Auth includes CSRF protection by default. Always use the auth client methods instead of raw fetch:

// ✓ Correct: uses auth client
await signIn.email({ email, password })

// ✗ Incorrect: bypasses CSRF protection
await fetch('/api/auth/sign-in/email', { method: 'POST', body: JSON.stringify({ email, password }) })
Route Rules and Page Meta are primarily for UX (redirects). Always protect your API endpoints with requireUserSession to ensure real security.