H3 and Nitro Setup

How to use auth-h3client without the Nuxt module, with setup examples for both H3 v1 and H3 v2, manual route registration, and middleware wiring.

The Nuxt module handles all wiring automatically, but auth-h3client also works as a standalone library with plain H3 or Nitro servers. The public API is identical across H3 v1 and v2: the same function names, the same configuration object, the same handler wrappers. The differences are in how H3 itself registers middleware and defines handlers.


Import paths

ImportH3 versionNotes
auth-h3client or auth-h3client/v1H3 v1 (1.15.x)Default, backwards compatible
auth-h3client/v2H3 v2 (2.0 beta)Opt-in for H3 v2 projects
auth-h3client/clientN/AVue/Nuxt composables only

Configuration

Call configuration() exactly once when your server starts. This sets the global configuration used by every handler and middleware in the library.

import { configuration } from 'auth-h3client/v1'
import { createStorage } from 'unstorage'
import memoryDriver from 'unstorage/drivers/memory'

const storage = createStorage({ driver: memoryDriver() })

configuration({
  server: {
    auth_location: {
      serverOrDNS: process.env.AUTH_HOST || 'localhost',
      port: Number(process.env.AUTH_PORT) || 4000,
    },
    hmac: {
      enableHmac: true,
      clientId: process.env.HMAC_CLIENT_ID!,
      sharedSecret: process.env.HMAC_SHARED_SECRET!,
    },
    ssl: { enableSSL: false },
    cryptoCookiesSecret: process.env.AUTH_CRYPTO_COOKIES!,
  },
  uStorage: {
    storage,
    cacheOptions: { successTtl: 60 * 60 * 24 * 30, rateLimitTtl: 10 },
  },
  onSuccessRedirect: 'https://app.example.com/dashboard',
  enableFireWallBans: false,
  logLevel: 'info',
})

See Configuration for the complete reference of all options.


H3 v1 setup

import { createApp, createRouter } from 'h3'
import {
  configuration,
  httpLogger,
  isIPValid,
  botDetectorMiddleware,
  generateCsrfCookie,
  useAuthRoutes,
  magicLinksRouter,
  useOAuthRoutes,
  bounceRouter,
  getAuthStatusHandler,
} from 'auth-h3client/v1'
  getApiListsController,

configuration({ /* ... */ })

const app = createApp()

// Global middleware (order matters)
httpLogger()(app)     // HTTP request logging
app.use(isIPValid)             // IP extraction and validation
app.use(botDetectorMiddleware) // Bot detection via IAM /check
app.use(generateCsrfCookie)   // CSRF cookie issuance

// Routes
const router = createRouter()
useAuthRoutes(router)           // POST /login, /signup, /logout
useOAuthRoutes(router)          // GET /oauth/:provider, /oauth/callback/:provider
bounceRouter(router)            // GET /auth/bounce
magicLinksRouter(router, 'api') // MFA, password reset, email change

// Auth status endpoint
router.get('/auth/users/authStatus', getAuthStatusHandler)

// Optional API token inventory endpoint
router.get('/api/auth/api-tokens', getApiListsController)

app.use(router)
The global isIPValid, botDetectorMiddleware, and generateCsrfCookie chain is for browser session routes. Do not mount that chain in front of routes that use defineAuthenticatePublicApi, because repeated machine-to-machine X-API-KEY verification traffic can hit the bot detector and escalate into rate limits or bans. Keep the chain for regular auth routes, getApiListsController, and defineApiManagementHandler, and add a path guard if you need both kinds of routes in the same app.

Route-level middleware in v1

import { defineEventHandler } from 'h3'
import { verifyCsrfCookie, contentType, limitBytes, signUpHandler, loginHandler, logoutHandler } from 'auth-h3client/v1'

export function useAuthRoutes(router: Router) {

  const signUpPipeline = defineEventHandler(async (event) => {
    await verifyCsrfCookie(event);
    await contentType('application/json')(event);
    await limitBytes(1024)(event);
    return signUpHandler(event);
  })

  router.post('/signup', signUpPipeline);

  const loginPipeline = defineEventHandler(async (event) => {
      await verifyCsrfCookie(event);
      await contentType('application/json')(event);
      await limitBytes(1024)(event);
      return loginHandler(event);
  });

  router.post('/login', loginPipeline);

  const logoutPipeline = defineEventHandler(async (event) => {
      await verifyCsrfCookie(event);
      await limitBytes(0)(event);
      return logoutHandler(event);
  })

  router.post('/logout', logoutPipeline)
}

H3 v2 setup

import { H3 } from 'h3'
import {
  configuration,
  httpLogger,
  isIPValid,
  botDetectorMiddleware,
  generateCsrfCookie,
  useAuthRoutes,
  magicLinksRouter,
  useOAuthRoutes,
  bounceRouter,
  getAuthStatusHandler,
  getApiListsController,
} from 'auth-h3client/v2'

configuration({ /* ... */ })


const app = new H3()
app.register(httpLogger()) // v2 logger is a plugin: register directly
app.use(isIPValid)             // IP extraction and validation
app.use(botDetectorMiddleware) // Bot detection via IAM /check
app.use(generateCsrfCookie)   // CSRF cookie issuance

// Routes
useAuthRoutes(app);           // POST /login, /signup, /logout
useOAuthRoutes(app);          // GET /oauth/:provider, /oauth/callback/:provider
bounceRouter(app);            // GET /auth/bounce
magicLinksRouter(app, 'api'); // MFA, password reset, email change

// Auth status endpoint
app.get('/auth/users/authStatus', getAuthStatusHandler)

// Optional API token inventory endpoint
app.get('/api/auth/api-tokens', getApiListsController)

// Your handlers

Optional API token inventory route

getApiListsController gives you a ready-made GET endpoint for authenticated API token inventory reads. It runs the authenticated session pipeline, calls IAM /api/manage/list-metadata, and strips public_identifier out of every token before returning the payload.

If you also need write operations such as token creation, rotation, revocation, IP restriction updates, or privilege changes, build your POST routes with defineApiManagementHandler. For public machine-to-machine routes protected by X-API-KEY, use defineAuthenticatePublicApi. Both wrappers are documented in the Middleware Reference.

Route-level middleware in v2

H3 v2 passes middleware as the third argument to route methods:

import { defineHandler } from 'h3'
import { verifyCsrfCookie, contentType, limitBytes, signUpHandler, loginHandler, logoutHandler } from 'auth-h3client/v2'

export function useAuthRoutes(router: H3) {

  router.post('/signup', signUpHandler,
    { middleware: [verifyCsrfCookie, contentType('application/json'), limitBytes(1024)] },
  )
  
  router.post('/logout', logoutHandler, 
    {middleware: [verifyCsrfCookie, limitBytes(0)]}
  )

  router.post('/login', loginHandler,
  {middleware: [verifyCsrfCookie, contentType('application/json'), limitBytes(1024)]}
  )

}

Key differences between v1 and v2

AspectH3 v1H3 v2
Handler definitiondefineEventHandler(...)defineHandler(...)
Route middlewarerouter.use('/path', mw) chained{ middleware: [mw1, mw2] } as third arg
HTTP loggerhttpLogger()(app) (function call)app.register(httpLogger()) (plugin)
RedirectsendRedirect(event, url)redirect(event, url)
ErrorscreateError({ ... })throw new HTTPError({ ... })
Response statussetResponseStatus(event, code)event.res.status = code

The exported handler wrappers (defineAuthenticatedEventHandler, defineVerifiedCsrfHandler, etc.), route registrars, and utility functions have the same names and signatures in both versions.


Writing custom handlers

When writing your own handlers alongside the library's built-in routes:

H3 v1

import { defineEventHandler } from 'h3'
import { defineAuthenticatedEventHandler } from 'auth-h3client/v1'

router.get('/api/profile', defineAuthenticatedEventHandler(async (event) => {
  const { userId, roles } = event.context.authorizedData
  return { userId, roles }
}))

H3 v2

import { defineHandler } from 'h3'
import { defineAuthenticatedEventHandler } from 'auth-h3client/v2'

router.get('/api/profile', defineAuthenticatedEventHandler(async (event) => {
  const { userId, roles } = event.context.authorizedData
  return { userId, roles }
}))

The handler wrappers internally use the correct defineEventHandler or defineHandler from the version you import.


Nitro plugin setup

When using Nitro (without the full Nuxt module), configure the library in a Nitro plugin:

server/plugins/initAuth.ts
import { defineNitroPlugin } from "nitro";
import { configuration, httpLogger, useAuthRoutes, useOAuthRoutes, magicLinksRouter, bounceRouter } from 'auth-h3client';
import { configDefaults } from 'auth-h3client/server/templates'

export default defineNitroPlugin((nitro) => {
configuration({
  ...configDefaults,
    uStorage: {
      storage: useStorage('cache'),
      cacheOptions: {
          successTtl: 60 * 60 * 24 * 30, 
          rateLimitTtl: 10    
      }
    }
  });

httpLogger()(nitro.h3App);
nitro.h3App.use(isIPValid)
nitro.h3App.use(botDetectorMiddleware)
nitro.h3App.use(generateCsrfCookie)

useAuthRoutes(nitro.router);
useOAuthRoutes(nitro.router);
bounceRouter(nitro.router);
magicLinksRouter(nitro.router, 'api');
})
When using a newer version of nitro, use the same setup above, and check if that version uses the v2 version of h3, if so, import directly from auth-h3client/v2

The key point to understand here, it doesn't matter what framework (nuxt/nitro) or h3 version you use, is that configuration needs to be called ones, in your app entry point, the middlewares needs to be registered along side the routes in this exact order.

What the Nuxt module adds

If you use the Nuxt module instead of manual setup, it provides:

  • Auto-imports of all server utilities in the server/ directory
  • Auto-imports of client composables (useAuthData, executeRequest, getCsrfToken, useMagicLink)
  • Automatic registration of the auth status route at the configured authStatusUrl
  • Optional registration of an API token list route at registerApiRoute.path
  • The enableMiddleware option for global middleware activation only. Disabling it does not disable the auth status route or the optional API token list route.

See Nuxt Module for the Nuxt module setup.

Logo