Middleware Reference

Complete reference for all middleware, event handler wrappers, and request utilities exported by Auth H3 Client, covering authentication guards, CSRF enforcement, bot detection, body validation, and request enrichment.

Every middleware exported by auth-h3client is documented here. Import them from auth-h3client/v1 or auth-h3client/v2. When using the Nuxt module, all middleware is auto-imported inside the server/ directory and the global chain is wired automatically. For other setups, register the global middlewares and route-level wrappers explicitly as shown in the H3 or Nitro setup.

All middleware depends on a resolved configuration. Call configuration(...) exactly once at startup before any middleware runs.

For the global middleware stack order applied automatically, see Security: Request Lifecycle. For route-level middleware chains per registered endpoint, see the Routes Reference.


Authentication guards

These middleware and wrappers enforce token presence, session validity, and HMAC signature verification before your handler runs.

ensureValidCredentials

The core token rotation middleware. Checks the access token expiry state and rotates both the access and refresh tokens when needed by calling the IAM /auth/user/refresh-session endpoint. Deduplicates concurrent rotation attempts on the same session so that only one rotation call is sent to the IAM service even if multiple requests arrive simultaneously.

On success, sets event.context.accessToken and event.context.session. Returns a 202 MFA response rather than throwing when the session requires step-up verification.

Reads

SourceValue
__Secure-a cookieThe current access token
session cookieThe current refresh token
canary_id cookieThe visitor fingerprint for anomaly binding

Sets

TargetValue
event.context.accessTokenThe current (or newly rotated) access token string
event.context.sessionThe current session cookie value

Responses on failure

StatusMeaning
202MFA challenge, step-up verification required
401No valid session found
429IAM service rate limit exceeded
Do not call this middleware manually inside routes that already use defineAuthenticatedEventHandler or defineOptionalAuthenticationEvent. Both wrappers call it internally.

hmacSignatureMiddleware

Generates HMAC-SHA256 signature headers for every outbound request the module sends to the IAM service. Reads the current request method and URL path, computes the signature using the sharedSecret from the configuration, and stores the resulting headers on event.context.authHeaders. serviceToService reads those headers when forwarding requests to the IAM service.

Reads

SourceValue
Request methodHTTP verb of the current inbound request
Request URL pathPath and query string of the current inbound request

Sets

TargetValue
event.context.authHeaders{ 'X-Client-Id', 'X-Timestamp', 'X-Request-Id', 'X-Signature' }
This middleware only applies when enableHmac: true in the configuration. When disabled, no headers are added. See the HMAC guide for setup and the IAM HMAC documentation for the server-side signature verification.

CSRF middleware

generateCsrfCookie

Mints a signed __Host-csrf cookie when one is not already present on the request. The token is a 32-byte random hex string, signed with an expiring HMAC payload using cryptoCookiesSecret.

Cookie attributes

AttributeValue
Name__Host-csrf
HttpOnlyfalse (the client must read it to inject it as a header)
SameSiteStrict
Securetrue
MaxAge1800 seconds

Runs automatically in global middleware. Also runs on all GET magic link routes to prepare a fresh cookie for the page that will be rendered.


verifyCsrfCookie

Validates the __Host-csrf cookie signature and expiry, then checks that the X-CSRF-Token request header contains a value that matches the token stored inside the cookie payload. The comparison uses isSameBuffer for timing-safe equality to prevent timing side-channel attacks.

Reads

SourceValue
__Host-csrf cookieThe signed CSRF token
X-CSRF-Token headerThe raw token submitted by the client

Responses on failure

StatusCodeMeaning
403CSRF_MISSINGThe __Host-csrf cookie is absent
403CSRF_INVALIDCookie signature or expiry check failed
403TOKEN_INVALIDHeader value does not match the cookie payload

See CSRF for the double-submit pattern and executeRequest integration.


Bot detection middleware

isIPValid

Extracts and validates the client IP address from the request headers using net.isIP(). Does not make any network call. Runs as the first step in the global middleware chain.

Reads

SourceValue
Request headersClient IP address

Responses on failure

StatusMeaning
403Invalid or missing IP address

botDetectorMiddleware

Forwards the visitor fingerprint to the IAM service /check endpoint. Returns HTTP 403 when the visitor's bot score exceeds the configured ban threshold. When enableFireWallBans: true, calls banIp to block the IP at the operating system firewall level.

On the first request from a visitor, the middleware calls /check and sets __Host-dr_i_n, a signed host-only cookie. On every subsequent request where both __Host-dr_i_n and canary_id are present and the __Host-dr_i_n signature is valid, the middleware skips calling the IAM service entirely. If the signature verification fails, the request is rejected with 403 and code CANARY_TEMPERING.

This middleware must remain enabled whenever the IAM service is in use. The canary_id cookie issued by the IAM service and the __Host-dr_i_n cookie set by this middleware are required for session operations including token rotation. Disabling bot detection means these cookies are never set and subsequent IAM calls will fail.

Reads

SourceValue
canary_id cookieVisitor fingerprint forwarded to IAM /check
__Host-dr_i_n cookieSigned tracking cookie: if present and valid, skips IAM call

Sets

TargetValue
__Host-dr_i_n cookieHMAC-signed tracking cookie (HttpOnly, SameSite: Strict, Secure, 2-hour TTL)
event.context.trackingResultParsed JSON response from the IAM /check endpoint

Responses on failure

StatusCodeMeaning
403NOT_ALLOWEDVisitor bot score exceeds ban threshold
403CANARY_TEMPERING__Host-dr_i_n cookie signature verification failed
502AUTH_SERVER_ERRORIAM /check endpoint unreachable

See Bot Detection for the full flow and the enableFireWallBans configuration.


OAuth middleware

OAuthTokensValidations

Validates the OAuth provider callback before the success handler runs. Reads the authorization code and state from the request (query string for GET, form body for POST), verifies the signed state cookie, and for OIDC providers verifies the ID token signature against the provider's JWKS endpoint, the nonce, azp, and at_hash claims, and that userinfo.sub matches the ID token subject. On success, sets event.context.provider, event.context.userData, and event.context.accessToken.

Called automatically by useOAuthRoutes as the first middleware in the /oauth/callback/:provider chain. Export it directly when building a custom OAuth callback route.

Reads

SourceValue
state{provider} cookieSigned state token for CSRF protection during the OAuth flow
pkce_v{provider} cookiePKCE code verifier (when supportPKCE: true)
nonce{provider} cookieNonce for OIDC ID token verification
Query / form bodycode, state, error, iss

Sets

TargetValue
event.context.providerThe matched provider name from configuration
event.context.userDataUser info object from the OAuth provider
event.context.accessTokenOAuth access token from the provider

Responses on failure

StatusMeaning
302OAuth error from provider, redirects to redirectUrlOnError
400State mismatch, missing code, or token validation failure
500Token exchange or JWKS verification error

See OAuth and OIDC for provider configuration and the full OIDC verification sequence.


Request validation middleware

limitBytes(maxBytes)

Factory function. Returns a middleware that reads the raw request body and throws HTTP 403 with code INVALID_CONTENT_TYPE if the byte length exceeds maxBytes. Pass 0 to reject any body entirely. Applied before JSON parsing, so oversized payloads cannot consume memory during deserialization.

Import

import { limitBytes } from 'auth-h3client'

Signature

function limitBytes(maxBytes: number): EventHandler

Usage

await limitBytes(1024)(event)  // Reject bodies over 1 KB
await limitBytes(0)(event)     // Reject any body

Responses on failure

StatusMeaning
403Body exceeds maxBytes (error code: INVALID_CONTENT_TYPE)

contentType(expected)

Factory function. Returns a middleware that validates the Content-Type request header against the expected value. Throws HTTP 403 if the header is missing or does not match.

Import

import { contentType } from 'auth-h3client'

Signature

function contentType(expected: string): EventHandler

Usage

await contentType('application/json')(event)

Responses on failure

StatusCodeMeaning
403INVALID_CONTENT_TYPEContent-Type missing or does not match expected value

defineByteLimiterHandler(handler, limitBytesTo, method)

Higher-order wrapper that asserts the request method, reads the raw request body exactly once, rejects bodies larger than limitBytesTo, parses JSON into event.context.body, and then calls your handler. The API token management wrapper uses this parser so request-size checks happen before any action runs.

If the request body is empty, the wrapper leaves event.context.body undefined and continues. If Content-Length is present and already exceeds the limit, the request is rejected before the full body is processed.

Parameters

ParameterTypeDescription
handlerEventHandlerYour H3 handler
limitBytesTonumberMaximum accepted request body size in bytes
method'POST' | 'PUT' | 'PATCH'The only HTTP method accepted by the wrapper

Sets

TargetValue
event.context.bodyParsed JSON object, or undefined for an empty body

Responses on failure

StatusMeaning
400JSON parse failed
403Body exceeds limitBytesTo
405Wrong HTTP method
server/api/account/settings.post.ts
export default defineByteLimiterHandler(async (event) => {
  const body = event.context.body
  return { ok: true, body }
}, 2048, 'POST')

Event handler wrappers

Wrappers are higher-order functions that accept your handler and return a new handler with enforcement built in. Import them from auth-h3client/v1 or auth-h3client/v2. When using the Nuxt module, they are auto-imported inside the server/ directory.

defineAuthenticatedEventHandler(handler)

The standard wrapper for protected routes. Runs hmacSignatureMiddleware, then ensureValidCredentials (token rotation), then calls getCachedUserData against the IAM service. Populates event.context.authorizedData with the verified session data. Throws HTTP 401 on failure. Returns HTTP 202 with mfaRequired when the session requires step-up verification.

Sets

TargetValue
event.context.authorizedData{ authorized, userId?, roles?, ipAddress, userAgent, date, reason?, error?, message? }

Responses on failure

StatusMeaning
202MFA challenge, step-up verification required
401Not authenticated or invalid session
429Rate limit exceeded
500IAM service error
server/api/profile.get.ts
export default defineAuthenticatedEventHandler(async (event) => {
  const { userId, roles } = event.context.authorizedData
  return { userId, roles }
})

defineOptionalAuthenticationEvent(handler)

Attempts authentication using the same internal pipeline as defineAuthenticatedEventHandler. On success, populates event.context.authorizedData. On any auth failure other than a rate limit, sets event.context.authorizedData to undefined and continues to your handler as a guest. HTTP 429 responses are still propagated.

Sets

TargetValue
event.context.authorizedDataVerified session data, or undefined for unauthenticated requests

Responses on failure

StatusMeaning
429Rate limit exceeded (always propagated, even for guests)
server/api/posts/[id].get.ts
export default defineOptionalAuthenticationEvent(async (event) => {
  const user = event.context.authorizedData // undefined for guests
  return user ? { content: 'private', userId: user.userId } : { content: 'public' }
})

defineVerifiedCsrfHandler(handler)

Calls verifyCsrfCookie before running the handler. Does not check authentication. Use this when you need CSRF protection on a route that is accessible to both guests and authenticated users.

Responses on failure

StatusMeaning
403CSRF cookie missing, invalid, or header mismatch
server/api/contact.post.ts
export default defineVerifiedCsrfHandler(async (event) => {
  const body = await readBody(event)
  return { ok: true }
})

defineAuthenticatedEventPostHandlers(handler)

Combines defineAuthenticatedEventHandler, defineVerifiedCsrfHandler, and assertMethod('POST') in that order. The correct choice for any state-changing endpoint that requires authentication.

Responses on failure

StatusMeaning
202MFA challenge
401Not authenticated
403CSRF failure or wrong HTTP method
429Rate limit exceeded
server/api/account/settings.post.ts
export default defineAuthenticatedEventPostHandlers(async (event) => {
  const { userId } = event.context.authorizedData
  await updateSettings(userId, await readBody(event))
  return { ok: true }
})

defineAuthenticatePublicApi(handler, userPrivilege)

Protects a route with API token verification. The wrapper reads X-API-KEY from the incoming request, forwards it to the IAM /api/public/verify endpoint together with the privilege floor defined by userPrivilege, and only then calls your handler.

Do not place routes wrapped with defineAuthenticatePublicApi behind the Nuxt global auth middleware or a manual isIPValid -> botDetectorMiddleware -> generateCsrfCookie chain. These requests are machine-to-machine API-key verification calls, so applying the browser session middleware can lead to bot-detector rate limits or bans. Keep the global middleware for regular auth routes, getApiListsController, and defineApiManagementHandler, because those browser session flows still need bot detection and the CSRF cookie.

On success, the wrapper stores the verified token metadata on event.context.apiVerification. On failure, it returns a normalized JSON response instead of throwing.

Reads

SourceValue
X-API-KEY headerRaw API token presented by the caller
userPrivilege argumentMinimum privilege level your route requires

Sets

TargetValue
event.context.apiVerification{ name, tokenId, userId, createdAt, expiresAt, lastUsed, usageCount, providedPrivilege }

Responses on failure

StatusMeaning
401API key missing
429Too many invalid verification attempts
IAM statusIAM verification rejected the token
500IAM service unreachable or returned no response
server/api/public/data.get.ts
export default defineAuthenticatePublicApi(async (event) => {
  const token = event.context.apiVerification
  return { ok: true, tokenId: token.tokenId, userId: token.userId }
}, 'demo')

defineApiManagementHandler(handler, allowedPrivilege, updateToNewPrivilege?)

Builds a complete authenticated POST pipeline for API token management routes. It wraps defineAuthenticatedEventPostHandlers(...) with defineByteLimiterHandler(..., 2000, 'POST'), validates event.context.params against the supported actions, and then proxies the request to the IAM API token management endpoints. This means the raw request body is size-checked and parsed into event.context.body before the action-specific logic runs.

Supported actions are new-token, revoke, metadata, rotate, ip-restriction-update, and privilege-update.

The wrapper rejects list-metadata on POST because token listing is handled by getApiListsController over GET.

For every action except new-token, the wrapper first calls /api/manage/list-metadata to resolve the token's public_identifier. This means the client submits only tokenId, and the wrapper maps that token ID to publicIdentifier and name before it calls IAM.

allowedPrivilege controls the privilege that will be assigned to newly created tokens. updateToNewPrivilege is optional, but it must be provided if you want the privilege-update action to succeed.

Accepted request bodies

ActionBody accepted by defineApiManagementHandlerIAM body sent by the wrapperSee
new-token{ name, prefix, ipv4?, expires? }{ name, prefix, ipv4?, expires?, privilege: allowedPrivilege }Creating Tokens
revoke{ tokenId }{ tokenId, publicIdentifier, name }Revocation
metadata{ tokenId }{ tokenId, publicIdentifier, name }Metadata
rotate{ tokenId }{ tokenId, publicIdentifier, name }Rotation
ip-restriction-update{ tokenId, ipv4? }{ tokenId, publicIdentifier, name, ipv4? }IP Restriction
privilege-update{ tokenId }{ tokenId, publicIdentifier, name, newPrivilege: updateToNewPrivilege }Privileges
list-metadataRejected on POSTNot sentToken Listing

The wrapper schema requires prefix on new-token and validates ip-restriction-update with an optional ipv4 array. The client request body never supplies privilege or newPrivilege. Your application decides those values through the allowedPrivilege and updateToNewPrivilege arguments.

On new-token, IAM returns rawPublicId in addition to the raw key. The wrapper removes rawPublicId before it populates event.context.newApiToken.

Sets

Actionevent.context fieldShape
new-tokennewApiToken{ rawApiKey, expiresAt }
ip-restriction-updateipRestrictionUpdate{ msg }
privilege-updateprivilegeUpdate{ msg }
revokerevokestring or { msg, invalidedTokenId, userId }
metadataextensiveMetadata{ tokenMeta, counts }
rotaterotate{ msg, newRawToken, newExpiry }

Responses on failure

StatusMeaning
202MFA challenge pending
400Invalid JSON body or list-metadata attempted on POST
401Session missing, unauthorized action, or token not owned by the user
403CSRF failure, oversized body, or privilege-update not permitted
404Invalid route action or invalid action-specific input
429Rate limited by IAM
IAM statusIAM action failed and returned { ok: false, reason }
server/api/auth/api-tokens/[action].post.ts
export default defineApiManagementHandler(async (event) => {
  const action = event.context.params?.action

  if (action === 'new-token') {
    return { ok: true, data: event.context.newApiToken }
  }

  if (action === 'metadata') {
    return { ok: true, data: event.context.extensiveMetadata }
  }

  return { ok: true }
}, 'demo', 'protected')

defineVerifiedMagicLinkGetHandler(handler)

Validates incoming magic link query parameters before running the handler. Enforces GET method, checks that the canary_id, session, and __Secure-a cookies are present, validates the query string against VerificationLinkSchema (visitor, token, random, reason), and calls the IAM service to verify the link. Populates event.context.link and event.context.reason on success.

Sets

TargetValue
event.context.linkThe link action type string (e.g., 'Custom MFA')
event.context.reasonThe link reason string

Responses on failure

StatusMeaning
400Link invalid, expired, or already used (forwarded from IAM)
405Not a GET request
401Required session cookies missing
server/api/auth/verify-custom.get.ts
export default defineVerifiedMagicLinkGetHandler(async (event) => {
  const { reason } = event.context
  return { ok: true, reason }
})
This wrapper does not validate a CSRF token. GET requests arrive directly from an email link, where no __Host-csrf cookie will be present yet.

defineMfaCodeVerifierHandler(handler)

Validates an MFA code submission after a magic link is verified. Enforces POST method, verifies the CSRF cookie, limits the request body to 8 MB, validates link query parameters against VerificationLinkSchema, reads event.context.body.code (a 7-digit numeric string), and sends the code to the IAM service for verification. On success, new tokens are issued and applied to the response cookies, and event.context.limitedMetaData is populated with the verified session metadata before your handler runs.

Reads

SourceValue
event.context.body.codeThe 7-digit OTP code submitted by the user
Query parameterstoken, random, reason, visitor

Sets

TargetValue
event.context.limitedMetaDataVerified session metadata returned by the IAM service

Responses on failure

StatusMeaning
400Malformed code, invalid or expired code, or link parameter mismatch
403CSRF failure or user banned
405Not a POST request
403Body exceeds 8 MB (error code: INVALID_CONTENT_TYPE)
server/api/auth/verify-code.post.ts
export default defineMfaCodeVerifierHandler(async (event) => {
  // Code verified, new tokens already applied to the response
  return { ok: true }
})

defineDeduplicatedEventHandler(handler)

Wraps a handler with request-level deduplication using lockAsyncAction. Concurrent requests with the same session identity are coalesced so that only one execution runs at a time. Results are cached briefly to serve requests that arrive after the first completes.

The built-in login, logout, signup, password reset, and MFA handlers already use this wrapper internally.

server/api/checkout.post.ts
export default defineDeduplicatedEventHandler(async (event) => {
  await processCheckout(event)
  return { ok: true }
})

Setup

defineAuthConfiguration(nitro, config) v1 only

One-call Nitro plugin setup function. Validates and freezes the configuration, registers httpLogger, mounts all four route registrars (useAuthRoutes, useOAuthRoutes, bounceRouter, magicLinksRouter with 'api' prefix) on nitro.router, and logs a startup confirmation. Use this inside a Nitro plugin to wire everything in a single call instead of registering each piece manually.

server/plugins/auth.ts
import { defineAuthConfiguration } from 'auth-h3client/v1'

export default defineNitroPlugin((nitro) => {
  defineAuthConfiguration(nitro, {
    server: { auth_location: 'https://iam.example.com' },
    // ...
  })
})

Parameters

ParameterTypeDescription
nitroNitroAppThe Nitro app instance provided by the plugin callback
configConfigurationThe full configuration object, passed to configuration(config) internally
This function is only exported from auth-h3client/v1. For H3 v2 setups, call configuration() and each route registrar individually.

Error utilities

throwHttpError(log, event, code, status, title, message)

Logs an error with structured context and throws an H3 error with the given status, title, and message. Use this inside custom handlers to produce consistent JSON error responses.

Import

import { throwHttpError } from 'auth-h3client'

Parameters

log
Logger required
A pino logger instance. Use getLogger().child({...}) to create a scoped logger.
event
H3Event required
The current H3 event.
code
string required
An internal error code string for structured logging.
status
number required
The HTTP status code.
title
string required
The public error title.
message
string required
The public error message.
throwHttpError(log, event, 'FORBIDDEN', 403, 'Forbidden', 'Access denied')

notFoundHandler

Returns a 404 JSON response. Used as a fallback inside handlers that receive an invalid magic link or an unknown route.


Logging middleware

httpLogger()

Returns an H3 middleware function that logs every request and response as structured JSON using pino. Register it on your H3 app or Nitro instance during startup, or rely on the Nuxt module to register it automatically. See Logging for v1 and v2 registration examples.

Features

  • Assigns a unique X-Request-Id header per request (or passes through an existing one)
  • Selects log level by response status: info for 2xx/3xx, warn for 4xx, error for 5xx
  • Skips logging for static asset paths

See Logging for configuring the logger level.


Wrapper quick reference

WrapperHMACToken rotationSession checkCSRFMethod
defineAuthenticatedEventHandlerYesYesYesNoAny
defineOptionalAuthenticationEventYesYesYes (optional)NoAny
defineVerifiedCsrfHandlerNoNoNoYesAny
defineAuthenticatedEventPostHandlersYesYesYesYesPOST only
defineAuthenticatePublicApiYesNoAPI key verificationNoAny
defineApiManagementHandlerYesYesYesYesPOST only
defineVerifiedMagicLinkGetHandlerNoNoNoNoGET only
defineMfaCodeVerifierHandlerNoNoNoYesPOST only
defineDeduplicatedEventHandlerNoNoNoNoAny
Logo