H3 logoH3

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}.

H3 logo

H3  Universal, Tiny, and Fast Servers.