Skip to content

OAuth Event Handlers ​

Define OAuth provider endpoints using event handler functions.

Overview ​

Nuxt Aegis provides specialized event handlers for each OAuth provider. These handlers create dual-purpose endpoints that handle both OAuth initiation and callback.

The shared handlers automatically protect every login, including Mock, with a generated state and an independent per-attempt proof cookie. Callbacks containing code or error require a valid pending transaction before token exchange or authentication hooks. Unsolicited callbacks, replay, malformed/repeated state, and missing/wrong proof are rejected. Provider denial consumes a correlated transaction; a token-exchange failure requires a fresh login.

An initial redirectTo must be a local application path. External origins, protocol-relative paths, backslashes, and control characters are rejected. The destination is restored from storage, ignoring callback redirect metadata. An initial application state is stored as metadata only, not sent as security state or exposed through a new callback API. authorizationParams.state is protected.

Manual HTTP clients must retain the initiation proof cookie and send it only to the application's provider callback, never to the OAuth provider. Browser navigation does this automatically. Cookie-free CLI OAuth is not yet provided; issued JWTs remain usable through cookie-free bearer requests. See single-process storage limits.

Callbacks receive the portable RequestEvent from nuxt/server. Use event.req, event.url, and event.res.headers instead of event.node or event.path. Import helpers explicitly from nuxt/server, even inside Aegis callbacks.

onError(event, error) receives a NuxtErrorLike with a numeric status, optional statusText, and the original exception as cause when it is normalized. It may return a string, a Web Response, or nothing, synchronously or asynchronously. Return redirect bodies rather than discarding them:

typescript
import { sendRedirect } from 'nuxt/server'

export default defineOAuthGoogleEventHandler({
  onError: (event, error) => {
    console.error('OAuth failed:', error.status)
    return sendRedirect(event, '/auth/callback?error=authentication_failed')
  },
})

Provider Event Handlers ​

defineOAuthGoogleEventHandler(config) ​

Define a Google OAuth event handler.

Type Signature:

typescript
function defineOAuthGoogleEventHandler(config: {
  config?: {
    clientId?: string
    clientSecret?: string
    scope?: string[]
    authorizationParams?: Record<string, string>
  }
  customClaims?: Record<string, unknown> | CustomClaimsCallback
  onUserInfo?: UserInfoCallback
  onSuccess?: SuccessCallback
}): EventHandler

Example:

typescript
// server/routes/auth/google.get.ts
export default defineOAuthGoogleEventHandler({
  config: {
    scope: ['openid', 'email', 'profile'],
    authorizationParams: {
      access_type: 'offline',
      prompt: 'consent',
    },
  },
  customClaims: async (user, tokens) => {
    return {
      role: await getUserRole(user.email),
      premium: await checkPremium(user.email),
    }
  },
  onSuccess: async ({ user, provider }) => {
    console.log('Google login:', user.email)
  },
})

defineOAuthAuth0EventHandler(config) ​

Define an Auth0 Universal Login event handler.

Type Signature:

typescript
function defineOAuthAuth0EventHandler(config: {
  config?: {
    domain?: string
    clientId?: string
    clientSecret?: string
    audience?: string
    scope?: string[]
    authorizationParams?: Record<string, string>
  }
  customClaims?: Record<string, unknown> | CustomClaimsCallback
  onUserInfo?: UserInfoCallback
  onSuccess?: SuccessCallback
}): EventHandler

Example:

typescript
// server/routes/auth/auth0.get.ts
export default defineOAuthAuth0EventHandler({
  config: {
    scope: ['openid', 'email', 'profile'],
    authorizationParams: {
      prompt: 'login',
      screen_hint: 'signup',
    },
  },
  customClaims: {
    role: 'user',
    accountType: 'standard',
  },
})

defineOAuthGitHubEventHandler(config) ​

Define a GitHub OAuth event handler.

Type Signature:

typescript
function defineOAuthGitHubEventHandler(config: {
  config?: {
    clientId?: string
    clientSecret?: string
    scope?: string[]
    authorizationParams?: Record<string, string>
  }
  customClaims?: Record<string, unknown> | CustomClaimsCallback
  onUserInfo?: UserInfoCallback
  onSuccess?: SuccessCallback
}): EventHandler

Example:

typescript
// server/routes/auth/github.get.ts
export default defineOAuthGitHubEventHandler({
  config: {
    scope: ['user:email', 'read:user'],
    authorizationParams: {
      allow_signup: 'true',
    },
  },
  customClaims: async (user, tokens) => {
    return {
      username: user.login,
      isVerified: user.verified,
    }
  },
})

defineOAuthMockEventHandler(config) ​

Define a Mock OAuth event handler for development/testing.

Type Signature:

typescript
function defineOAuthMockEventHandler(config: {
  config: {
    clientId: string
    clientSecret: string
    mockUsers: MockUser[]
    defaultUser?: number
    enableInProduction?: boolean
  }
  customClaims?: Record<string, unknown> | CustomClaimsCallback
  onUserInfo?: UserInfoCallback
  onSuccess?: SuccessCallback
}): EventHandler

Example:

typescript
// server/routes/auth/mock.get.ts
export default defineOAuthMockEventHandler({
  config: {
    clientId: 'mock-client',
    clientSecret: 'mock-secret',
    mockUsers: [
      {
        sub: 'mock:admin',
        email: 'admin@example.com',
        name: 'Admin User',
        role: 'admin',
      },
      {
        sub: 'mock:user',
        email: 'user@example.com',
        name: 'Regular User',
        role: 'user',
      },
    ],
    defaultUser: 0,
  },
})

Configuration Options ​

Common Options ​

OptionTypeDescription
configobjectProvider-specific configuration
customClaimsobject | functionCustom JWT claims
onUserInfofunctionUser info transformation callback
onSuccessfunctionSuccess callback

Config Object ​

Provider-specific configuration:

typescript
interface Config {
  clientId?: string
  clientSecret?: string
  scope?: string[]
  authorizationParams?: Record<string, string>
  // ... provider-specific options
}

Custom Claims ​

Static claims:

typescript
customClaims: {
  role: 'admin',
  premium: true,
}

Dynamic claims:

typescript
customClaims: async (user, tokens) => {
  return {
    role: await getUserRole(user.email),
    permissions: await getPermissions(user.email),
  }
}

Callbacks ​

UserInfoCallback ​

Transform user data after fetching from OAuth provider:

typescript
type UserInfoCallback = (
  user: Record<string, unknown>,
  tokens: {
    access_token: string
    refresh_token?: string
    id_token?: string
    expires_in?: number
  },
  event: RequestEvent
) => Record<string, unknown> | Promise<Record<string, unknown>>

Example:

typescript
onUserInfo: async (user, tokens, event) => {
  // Add custom fields
  user.customField = 'value'
  
  // Normalize data
  if (user.avatar_url) {
    user.picture = user.avatar_url
    delete user.avatar_url
  }
  
  return user
}

SuccessCallback ​

Handle successful authentication:

typescript
type SuccessCallback = (payload: {
  user: Record<string, unknown>
  tokens: {
    access_token: string
    refresh_token?: string
    id_token?: string
    expires_in?: number
  }
  provider: string
  event: RequestEvent
}) => void | Promise<void>

Example:

typescript
onSuccess: async ({ user, provider, event }) => {
  // Save to database
  await db.users.upsert({
    where: { email: user.email },
    update: { lastLogin: new Date() },
    create: {
      id: user.sub,
      email: user.email,
      name: user.name,
      provider,
    },
  })
  
  // Send analytics
  await analytics.track('user_login', {
    userId: user.sub,
    provider,
  })
}

Complete Example ​

typescript
// server/routes/auth/google.get.ts
export default defineOAuthGoogleEventHandler({
  config: {
    scope: ['openid', 'email', 'profile', 'https://www.googleapis.com/auth/calendar.readonly'],
    authorizationParams: {
      access_type: 'offline',
      prompt: 'consent',
      hd: 'example.com', // Restrict to domain
    },
  },
  
  customClaims: async (user, tokens) => {
    // Fetch user profile from database
    const profile = await db.users.findUnique({
      where: { email: user.email },
      include: { organization: true },
    })
    
    return {
      role: profile?.role || 'user',
      permissions: profile?.permissions || [],
      organizationId: profile?.organization?.id,
      premium: profile?.premium || false,
    }
  },
  
  onUserInfo: async (user, tokens, event) => {
    // Fetch additional data from Google
    const calendar = await $fetch('https://www.googleapis.com/calendar/v3/users/me/calendarList', {
      headers: {
        Authorization: `Bearer ${tokens.access_token}`,
      },
    })
    
    user.hasCalendar = calendar.items.length > 0
    
    return user
  },
  
  onSuccess: async ({ user, provider, event }) => {
    // Update user in database
    await db.users.upsert({
      where: { email: user.email },
      update: {
        name: user.name,
        picture: user.picture,
        lastLogin: new Date(),
      },
      create: {
        id: user.sub,
        email: user.email,
        name: user.name,
        picture: user.picture,
        provider,
        createdAt: new Date(),
      },
    })
    
    // Send welcome email for new users
    const isNewUser = await db.users.count({
      where: { email: user.email },
    }) === 1
    
    if (isNewUser) {
      await sendWelcomeEmail(user.email)
    }
    
    // Track analytics
    await analytics.track('user_authenticated', {
      userId: user.sub,
      provider,
      isNewUser,
    })
  },
})

Released under the MIT License.