Security

H3 security utilities.

Authentication

basicAuth(opts)

Create a basic authentication middleware.

Example:

import { H3, serve, basicAuth } from "h3";
const auth = basicAuth({ password: "test" });
app.get("/", (event) => `Hello ${event.context.basicAuth?.username}!`, [auth]);
serve(app, { port: 3000 });

requireBasicAuth(event, opts)

Apply basic authentication for current request.

Example:

import { defineHandler, requireBasicAuth } from "h3";
export default defineHandler(async (event) => {
  await requireBasicAuth(event, { password: "test" });
  return `Hello, ${event.context.basicAuth.username}!`;
});

Session

clearSession(event, config)

Clear the session data for the current request.

getSession(event, config)

Get the session for the current request.

sealSession(event, config)

Encrypt and sign the session data for the current request.

unsealSession(_event, config, sealed)

Decrypt and verify the session data for the current request.

updateSession(event, config, update?)

Update the session data for the current request.

useSession(event, config)

Create a session manager for the current request.

Fingerprint

getRequestFingerprint(event, opts)

Get a unique fingerprint for the incoming request.

CORS

appendCorsHeaders(event, options)

Append CORS headers to the response.

appendCorsPreflightHeaders(event, options)

Append CORS preflight headers to the response.

handleCors(event, options)

Handle CORS for the incoming request.

If the incoming request is a CORS preflight request, it will append the CORS preflight headers and send a 204 response.

If return value is not false, the request is handled and no further action is needed.

Example:

const app = new H3();
app.all("/", async (event) => {
  const corsRes = handleCors(event, {
    origin: "*",
    preflight: {
      statusCode: 204,
    },
    methods: "*",
  });
  if (corsRes !== false) {
    return corsRes;
  }
  // Your code here
});

isCorsOriginAllowed(origin, options)

Check if the origin is allowed.

isPreflightRequest(event)

Check if the incoming request is a CORS preflight request.

Path

isCanonicalPath(path, opts?)

Whether path is already canonical under opts — i.e. {@link resolveDotSegments} would return it unchanged. Exact in both directions: true if and only if resolveDotSegments(path, opts) === path.

This is the resolver's own fast-path guard, exported so a caller that canonicalizes on a hot path (per-request scope or rule matching) can skip the call — and any work derived from it — without keeping its own copy of what the resolver decodes. Such a copy goes stale silently, and a missed canonicalization in a scope check is a bypass, not a perf bug.

Pass the same options as the later {@link resolveDotSegments} call, or stricter ones: decodeSlashes/mergeSlashes only add triggers, so true with both enabled implies true in every mode. Checking one mode and resolving in another voids the guarantee.

Takes a bare pathname. Like the resolver, it has no notion of a query or hash and scans one as if it were path, so /a?next=/../b is reported non-canonical (and would resolve to /b).

resolveDotSegments(path, opts?)

Resolve . and .. segments in a path, without ever escaping above the root /. The result is always an absolute path with a single leading /, so it can never be protocol-relative (//host).

Also decodes percent-encoded dot segments at any %25-nesting depth (%2e, %252e, ...) and normalizes \ to /, so encoded or backslash-based traversal (e.g. %2e%2e/, ..\..\) is caught the same way as a literal ../.

%2f/%5c (encoded path separators) are left untouched by default — see {@link ResolveDotSegmentsOptions.decodeSlashes}.

Only ./.. resolution and the decodes above alter the string; every other percent-encoding (%20, non-ASCII, %3A, and any %2e not forming a whole segment) is left intact, so the result stays in the same representation as an un-decoded event.url.pathname and matches routes/rules consistently. A trailing ./.. resolves to a directory and keeps its trailing slash (/a/b/.. -> /a/, /a/. -> /a/), per RFC 3986 §5.2.4 and matching what a WHATWG/nginx downstream resolves — so a scope check sees the directory form, not its file-form sibling. Interior empty segments are preserved (/a//b stays /a//b) — like WHATWG, this never merges slashes, so empty segments survive rather than collapsing. The one exception is a leading run: it is always clamped to a single / (WHATWG would keep //host), so only the leading slash is guaranteed single and a consumer doing exact prefix matching should normalize its allowlist the same way. To collapse interior runs too (the reading a slash-merging downstream resolves), see {@link ResolveDotSegmentsOptions.mergeSlashes}.