Files
skills/docs/api-stack/api/api-plugin-auth/src/middleware/require-email-verified.md
2026-05-29 08:20:56 +01:00

2.3 KiB

middleware/require-email-verified

Purpose

requireEmailVerified() is the Hono middleware factory that gates a route on the principal's email having been verified. Routes that trigger sensitive operations (password change, billing portal, account deletion) use this guard so unverified accounts cannot move forward without first completing the verification flow.

Features

  • Reads c.get("user") directly — no Better-Auth lookup per request.
  • Two failure modes: AUTH_UNAUTHENTICATED (401) for anonymous callers and AUTH_EMAIL_NOT_VERIFIED (403) for unverified ones.
  • Strict boolean check: emailVerified !== true fails, so undefined / null are treated as unverified.

Usage: Basic

import {
    requireAuth,
    requireEmailVerified,
} from "@suaveplan/api-plugin-auth/middleware";

app.use("/billing/*", requireAuth(), requireEmailVerified());

Usage: Advanced

Use the middleware as a precondition for routes that trigger high-trust workflows. Combine with requireRole so admin routes require BOTH verified email AND the admin role:

import {
    requireAuth,
    requireEmailVerified,
    requireRole,
} from "@suaveplan/api-plugin-auth/middleware";

app.use(
    "/admin/users/:id/delete",
    requireAuth(),
    requireEmailVerified(),
    requireRole("admin")
);

API Reference

function requireEmailVerified(): MiddlewareHandler

  • Throws AuthError(AUTH_UNAUTHENTICATED) (401) when c.get("user") is null/undefined.
  • Throws AuthError(AUTH_EMAIL_NOT_VERIFIED) (403) when principal.emailVerified !== true.
  • Calls next() otherwise.

Implementation Notes

  • The check uses !== true rather than === false so an explicitly undefined / null emailVerified (which the default principal mapping should not emit, but a custom mapping might) is treated as unverified. Failing closed is the right default for an auth gate.
  • The middleware does not auto-trigger a re-send of the verification email — that is the route handler's responsibility once the 403 reaches the client.
  • The 403 error carries context.statusCode: 403 for the error handler; no other context fields are attached because the principal identity does not need to be re-emitted on a 403 (it is already available in the trace span).