
# Security

> H3 security utilities.

## Authentication

<!-- automd:jsdocs src="../../src/utils/auth.ts" -->

### `basicAuth(opts)`

Create a basic authentication middleware.

**Example:**

```ts
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:**

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

<!-- /automd -->

## Session

<!-- automd:jsdocs src="../../src/utils/session.ts" -->

### `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.

<!-- /automd -->

## Fingerprint

<!-- automd:jsdocs src="../../src/utils/fingerprint.ts" -->

### `getRequestFingerprint(event, opts)`

Get a unique fingerprint for the incoming request.

<!-- /automd -->

## CORS

<!-- automd:jsdocs src="../../src/utils/cors.ts" -->

### `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:**

```ts
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.

<!-- /automd -->

## Path

<!-- automd:jsdocs src="../../src/utils/path.ts" -->

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

<!-- /automd -->
