Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/1.docs/5.routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ export default defineHandler((event) => {

You can use the [utilities available in H3](https://h3.dev/guide/basics/error) to handle errors in both routes and middlewares.

The way errors are sent back to the client depends on the environment. In development, requests with an `Accept` header of `text/html` (such as browsers) will receive a HTML error page. In production, errors are always sent in JSON.
The way errors are sent back to the client depends on the environment. In development, requests with an `Accept` header of `text/html` (such as browsers) will receive a HTML error page. In production, errors are always sent in JSON.

This behaviour can be overridden by some request properties (e.g.: `Accept` or `User-Agent` headers).

Expand All @@ -324,7 +324,7 @@ See [`inlineDynamicImports`](/config#inlinedynamicimports) to bundle everything

## Route rules

Nitro allows you to add logic at the top-level for each route of your configuration. It can be used for redirecting, proxying, caching and adding headers to routes.
Nitro allows you to add logic at the top-level for each route of your configuration. It can be used for redirecting, proxying, caching, authentication, and adding headers to routes.

It is a map from route pattern (following [rou3](https://github.com/h3js/rou3)) to route options.

Expand All @@ -351,6 +351,8 @@ export default defineConfig({
'/old-page/**': { redirect: '/new-page/**' },
'/proxy/example': { proxy: 'https://example.com' },
'/proxy/**': { proxy: '/api/**' },
'/admin/**': { basicAuth: { username: 'admin', password: 'supersecret' } },
}
});
```

2 changes: 1 addition & 1 deletion src/build/virtual/routing.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Nitro, NitroEventHandler, NitroRouteRules } from "nitro/types";

export const RuntimeRouteRules = ["headers", "redirect", "proxy", "cache"] as string[];
export const RuntimeRouteRules = ["headers", "redirect", "proxy", "cache", "basicAuth"] as string[];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Credentials will be embedded in plaintext in the compiled bundle.

Adding "basicAuth" to RuntimeRouteRules means serializeRouteRule (line 101) will call JSON.stringify(options) on the basicAuth options object, embedding password and username values directly as a string literal in the generated virtual module and therefore in the compiled server bundle.

Unlike other entries in RuntimeRouteRules (headers, redirect, proxy, cache), basicAuth explicitly carries secrets. Any party with read access to the bundle artifact (e.g., a deployed Docker image layer, edge-worker script, or accidental bundle exposure) can extract the credentials trivially.

Consider documenting this trade-off prominently and/or guiding users to supply credentials via environment variables, e.g.:

-  "/admin/**": { basicAuth: { username: "admin", password: "secret" } }
+  "/admin/**": { basicAuth: { username: process.env.BASIC_AUTH_USER, password: process.env.BASIC_AUTH_PASS } }

At minimum, a warning during the build step (via consola.warn) when basicAuth options contain a non-empty literal password would alert users who accidentally hardcode credentials. As per coding guidelines, consola should be used for logging in build/dev code.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/build/virtual/routing.ts` at line 3, RuntimeRouteRules currently includes
"basicAuth" which causes serializeRouteRule to JSON.stringify basicAuth options
and embed plaintext credentials in the generated virtual module; update the code
to avoid embedding secrets by either removing "basicAuth" from RuntimeRouteRules
or changing serializeRouteRule to treat basicAuth specially: detect when
ruleName === "basicAuth" and if options contains a literal non-empty
password/username do not stringify them into the bundle but instead emit a
consola.warn (use consola.warn) informing the user to supply credentials via
environment variables or runtime secret injection, and when emitting the route
rule reference ensure it references placeholders or env-based lookups rather
than inlined credentials; reference RuntimeRouteRules and serializeRouteRule in
your change so reviewers can find the logic.


export default function routing(nitro: Nitro) {
return {
Expand Down
16 changes: 13 additions & 3 deletions src/runtime/internal/route-rules.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { proxyRequest, redirect as sendRedirect } from "h3";
import type { EventHandler, Middleware } from "h3";
import { proxyRequest, redirect as sendRedirect, requireBasicAuth } from "h3";
import type { BasicAuthOptions, EventHandler, Middleware } from "h3";
import type { MatchedRouteRule, NitroRouteRules } from "nitro/types";
import { joinURL, withQuery, withoutBase } from "ufo";
import { defineCachedHandler } from "./cache.ts";

// Note: Remember to update RuntimeRouteRules in src/routing.ts when adding new route rules
// Note: Remember to update RuntimeRouteRules in src/build/virtual/routing.ts when adding new route rules

type RouteRuleCtor<T extends keyof NitroRouteRules> = (m: MatchedRouteRule<T>) => Middleware;

Expand Down Expand Up @@ -79,3 +79,13 @@ export const cache: RouteRuleCtor<"cache"> = ((m) =>
}
return cachedHandler(event);
}) satisfies RouteRuleCtor<"cache">;

// basicAuth auth route rule
export const basicAuth: RouteRuleCtor<"auth"> = ((m) =>
async function authRouteRule(event, next) {
if (!m.options) {
return;
}
await requireBasicAuth(event, m.options as BasicAuthOptions);
return next();
}) satisfies RouteRuleCtor<"auth">;
3 changes: 2 additions & 1 deletion src/types/route-rules.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Middleware, ProxyOptions } from "h3";
import type { Middleware, ProxyOptions, BasicAuthOptions } from "h3";
import type { ExcludeFunctions, IntRange } from "./_utils.ts";
import type { CachedEventHandlerOptions } from "./runtime/index.ts";

Expand All @@ -11,6 +11,7 @@ export interface NitroRouteConfig {
prerender?: boolean;
proxy?: string | ({ to: string } & ProxyOptions);
isr?: number /* expiration */ | boolean | VercelISRConfig;
basicAuth?: Pick<BasicAuthOptions, "password" | "username" | "realm"> | false;

// Shortcuts
cors?: boolean;
Expand Down
4 changes: 4 additions & 0 deletions test/fixture/nitro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ export default defineConfig({
"/rules/_/cached/**": { swr: true },
"/api/proxy/**": { proxy: "/api/echo" },
"/cdn/**": { proxy: "https://cdn.jsdelivr.net/**" },
"/rules/basic-auth/**": {
basicAuth: { username: "admin", password: "secret", realm: "Secure Area" },
},
"/rules/basic-auth/no-auth/**": { basicAuth: false },
"**": { headers: { "x-test": "test" } },
},
prerender: {
Expand Down
28 changes: 28 additions & 0 deletions test/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,34 @@
expect(headers).toMatchObject(expectedHeaders);
});

describe("handles route rules - basic auth", () => {
it("rejects request with bad creds", async () => {
const { status, headers } = await callHandler({
url: "/rules/basic-auth",
headers: {
Authorization: "Basic " + btoa("user:wrongpass"),
},
});
expect(status).toBe(401);
expect(headers["www-authenticate"]).toBe('Basic realm="Secure Area"');

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/standard.test.ts > nitro:standard > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/node.test.ts > nitro:preset:node-middleware > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/nitro-dev.test.ts > nitro:preset:nitro-dev > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/netlify.test.ts > nitro:preset:netlify > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/deno-server.test.ts > nitro:preset:deno-server > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/cloudflare-pages.test.ts > nitro:preset:cloudflare-pages > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/cloudflare-module.test.ts > nitro:preset:cloudflare-module > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/bun.test.ts > nitro:preset:bun > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/aws-lambda.test.ts > nitro:preset:aws-lambda-v1 > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/aws-lambda.test.ts > nitro:preset:aws-lambda-v2 > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/standard.test.ts > nitro:standard > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/node.test.ts > nitro:preset:node-middleware > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/nitro-dev.test.ts > nitro:preset:nitro-dev > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/netlify.test.ts > nitro:preset:netlify > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/deno-server.test.ts > nitro:preset:deno-server > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/cloudflare-pages.test.ts > nitro:preset:cloudflare-pages > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/cloudflare-module.test.ts > nitro:preset:cloudflare-module > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/bun.test.ts > nitro:preset:bun > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/aws-lambda.test.ts > nitro:preset:aws-lambda-v1 > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/aws-lambda.test.ts > nitro:preset:aws-lambda-v2 > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/standard.test.ts > nitro:standard > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/node.test.ts > nitro:preset:node-middleware > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/nitro-dev.test.ts > nitro:preset:nitro-dev > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/netlify.test.ts > nitro:preset:netlify > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/deno-server.test.ts > nitro:preset:deno-server > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/cloudflare-pages.test.ts > nitro:preset:cloudflare-pages > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/cloudflare-module.test.ts > nitro:preset:cloudflare-module > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/bun.test.ts > nitro:preset:bun > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/aws-lambda.test.ts > nitro:preset:aws-lambda-v1 > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rolldown (ubuntu-latest)

test/presets/aws-lambda.test.ts > nitro:preset:aws-lambda-v2 > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/standard.test.ts > nitro:standard > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/node.test.ts > nitro:preset:node-middleware > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/nitro-dev.test.ts > nitro:preset:nitro-dev > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/netlify.test.ts > nitro:preset:netlify > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/deno-server.test.ts > nitro:preset:deno-server > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/cloudflare-pages.test.ts > nitro:preset:cloudflare-pages > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/cloudflare-module.test.ts > nitro:preset:cloudflare-module > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/bun.test.ts > nitro:preset:bun > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/aws-lambda.test.ts > nitro:preset:aws-lambda-v1 > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43

Check failure on line 398 in test/tests.ts

View workflow job for this annotation

GitHub Actions / tests-rollup (ubuntu-latest)

test/presets/aws-lambda.test.ts > nitro:preset:aws-lambda-v2 > handles route rules - basic auth > rejects request with bad creds

AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality - Expected: "Basic realm=\"Secure Area\"" + Received: undefined ❯ test/tests.ts:398:43
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's find the fixture basicAuth config
find test -type f -name "*.ts" -o -name "*.js" | head -20

Repository: nitrojs/nitro

Length of output: 773


🏁 Script executed:

# Search for basicAuth in test fixtures
rg -n "basicAuth" test/ --type=ts -A5 -B2

Repository: nitrojs/nitro

Length of output: 721


🏁 Script executed:

# Search for WWW-Authenticate header handling in src
rg -n "WWW-Authenticate|www-authenticate" src/ --type=ts -A2 -B2

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check route-rules implementation
fd -t f "route-rules" src/ --exec cat -n {} \;

Repository: nitrojs/nitro

Length of output: 9644


🏁 Script executed:

# Look for basicAuth in route-rules
rg -n "basicAuth" src/ --type=ts -A5 -B2

Repository: nitrojs/nitro

Length of output: 1743


🏁 Script executed:

# Check the test file to see exact test setup at line 393-398
sed -n '393,398p' test/tests.ts

Repository: nitrojs/nitro

Length of output: 265


🏁 Script executed:

# Look for any h3 version or documentation in package.json
cat package.json | grep -A2 -B2 "h3"

Repository: nitrojs/nitro

Length of output: 460


🏁 Script executed:

# Check if there are any other basicAuth tests or examples in the codebase
rg -n "requireBasicAuth|basicAuth" test/ --type=ts -B2 -A2 | head -50

Repository: nitrojs/nitro

Length of output: 571


🏁 Script executed:

# Check if there's a test setup or fixture initialization that might affect headers
sed -n '389,415p' test/tests.ts

Repository: nitrojs/nitro

Length of output: 942


🏁 Script executed:

# Look for any callHandler function that processes the response
rg -n "callHandler" test/tests.ts -B3 -A10 | head -40

Repository: nitrojs/nitro

Length of output: 1496


🏁 Script executed:

# Check the complete callHandler implementation to see how headers are extracted
sed -n '203,240p' test/tests.ts

Repository: nitrojs/nitro

Length of output: 1175


🏁 Script executed:

# Look for the TestHandlerResult type definition to understand header structure
rg -n "TestHandlerResult" test/ --type=ts -B5 -A5

Repository: nitrojs/nitro

Length of output: 1364


🏁 Script executed:

# Check if there's any middleware or handler setup that might affect basicAuth
rg -n "basicAuth|requireBasicAuth" test/fixture/ --type=ts -A5

Repository: nitrojs/nitro

Length of output: 568


🏁 Script executed:

# Let's check h3 documentation or type definition for requireBasicAuth behavior
rg -n "requireBasicAuth" --type=ts -B3 -A10 | head -30

Repository: nitrojs/nitro

Length of output: 1427


🏁 Script executed:

# Check if there are other basicAuth tests that pass to understand expected behavior
rg -n "www-authenticate|WWW-Authenticate" test/ --type=ts -B5 -A5

Repository: nitrojs/nitro

Length of output: 617


🏁 Script executed:

# Let's check if there's any test output or debug logs that show actual header values
rg -n "Basic realm\|www-authenticate" test/ --type=ts

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check if there are any comments in the PR or test about expected h3 behavior
rg -n "h3|requireBasicAuth" test/tests.ts -B2 -A2 | grep -i "comment\|todo\|fixme\|note"

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Let's check what h3 version might have this issue or if there are any known issues
cat package.json | grep -E "h3|version"

Repository: nitrojs/nitro

Length of output: 250


🏁 Script executed:

# See if there's a test that sends no credentials to understand expected behavior difference
sed -n '380,420p' test/tests.ts

Repository: nitrojs/nitro

Length of output: 1489


🏁 Script executed:

# Check if there's any documentation or comments about the basicAuth implementation
head -20 src/runtime/internal/route-rules.ts

Repository: nitrojs/nitro

Length of output: 936


🏁 Script executed:

# Let's check if h3's requireBasicAuth is even being exported from h3 in package.json exports
rg -n "requireBasicAuth" package.json

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check if there are any setup/teardown issues or middleware that might strip headers
sed -n '140,200p' test/tests.ts | grep -A10 -B10 "describe\|middleware"

Repository: nitrojs/nitro

Length of output: 39


🌐 Web query:

h3 requireBasicAuth WWW-Authenticate header behavior

πŸ’‘ Result:

In h3 (the UnJS/h3 framework), requireBasicAuth(event, opts) enforces HTTP Basic Auth for the current request; when credentials are missing or invalid, it fails the request with HTTP 401 Unauthorized and (to comply with the HTTP auth challenge flow / to trigger browser credential prompts) it sends a WWW-Authenticate challenge for the Basic scheme. The docs list requireBasicAuth as the utility for applying Basic Auth to a request. [1]

Per the HTTP authentication spec, a 401 response for a protected resource must include at least one WWW-Authenticate header describing the challenge (e.g., Basic realm="..."). [2]

Sources:
[1] h3 Security utils (basicAuth, requireBasicAuth) β€” https://www.h3.dev/utils/security
[2] MDN: WWW-Authenticate header (401 must include it; Basic uses realm) β€” https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/WWW-Authenticate


🌐 Web query:

h3 2.0.1-rc.14 basicAuth implementation

πŸ’‘ Result:

In h3 2.0.1-rc.14, basicAuth() is a middleware factory for HTTP Basic Authentication that’s meant to be used either:

  • as route middleware: app.get("/", handler, [basicAuth({ password })]) [1], or
  • imperatively inside a handler via await requireBasicAuth(event, opts) [1].

What it does (behavior / flow)

At request time, the middleware:

  1. Reads the Authorization header (expects the Basic <base64> scheme).
  2. Decodes the base64 payload into username:password.
  3. Validates the provided password against the configured password (and/or other allowed credentials depending on options).
  4. On success, it stores auth info on the event context so you can access the username at event.context.basicAuth.username [1].
  5. On failure, it responds with 401 Unauthorized and a WWW-Authenticate: Basic … challenge header (standard Basic Auth behavior).

Security-related implementation details present in the v2 RCs

The v2 RC line includes hardening specifically for Basic Auth comparisons:

  • It uses random jitter and a constant-time string comparison to reduce timing side-channels (added in the 2.0.1-rc.9 fixes). [2]
  • In 2.0.1-rc.14, the jitter generator was further strengthened (β€œEnhance randomJitter function for cryptographic security”). [3]

Minimal β€œshape” of the implementation (pseudocode)

export function basicAuth(opts) {
  return async (event) => {
    await requireBasicAuth(event, opts) // throws / returns 401 response on failure
  }
}

Where requireBasicAuth(event, opts) performs the header parsing + decode, then validates using constant-time comparison plus jitter, and sets event.context.basicAuth = { username } on success. [1] [2] [3]

Sources: [1] (H3 security utils docs), [2] (release note mentioning jitter + constant-time compare), [3] (rc.14 release note about stronger random jitter).


www-authenticate header is missing from 401 response β€” blocking test failure across all CI environments

The test at line 398 expects the header but receives undefined, while the preceding line's 401 status check passes. This confirms the response has the correct status but not the required HTTP challenge header.

The fixture configuration is correct (realm: "Secure Area" at test/fixture/nitro.config.ts:110), and the route-rules implementation properly forwards the entire options objectβ€”including realmβ€”to h3's requireBasicAuth (src/runtime/internal/route-rules.ts:89). Per HTTP spec and h3 v2.0.1-rc.14 documentation, requireBasicAuth should emit a WWW-Authenticate header when credentials are invalid.

The actual root cause lies either in h3's requireBasicAuth behavior in rc.14 or in how the route-rule middleware chain applies basicAuth to the event. This requires investigation of the actual header value being returned or verification that the basicAuth route rule is properly invoking requireBasicAuth and propagating its response headers.

🧰 Tools
πŸͺ› GitHub Check: tests-rolldown (ubuntu-latest)

[failure] 398-398: test/presets/standard.test.ts > nitro:standard > handles route rules - basic auth > rejects request with bad creds
AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality

  • Expected:
    "Basic realm="Secure Area""
  • Received:
    undefined

❯ test/tests.ts:398:43


[failure] 398-398: test/presets/node.test.ts > nitro:preset:node-middleware > handles route rules - basic auth > rejects request with bad creds
AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality

  • Expected:
    "Basic realm="Secure Area""
  • Received:
    undefined

❯ test/tests.ts:398:43


[failure] 398-398: test/presets/nitro-dev.test.ts > nitro:preset:nitro-dev > handles route rules - basic auth > rejects request with bad creds
AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality

  • Expected:
    "Basic realm="Secure Area""
  • Received:
    undefined

❯ test/tests.ts:398:43


[failure] 398-398: test/presets/netlify.test.ts > nitro:preset:netlify > handles route rules - basic auth > rejects request with bad creds
AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality

  • Expected:
    "Basic realm="Secure Area""
  • Received:
    undefined

❯ test/tests.ts:398:43


[failure] 398-398: test/presets/deno-server.test.ts > nitro:preset:deno-server > handles route rules - basic auth > rejects request with bad creds
AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality

  • Expected:
    "Basic realm="Secure Area""
  • Received:
    undefined

❯ test/tests.ts:398:43


[failure] 398-398: test/presets/cloudflare-pages.test.ts > nitro:preset:cloudflare-pages > handles route rules - basic auth > rejects request with bad creds
AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality

  • Expected:
    "Basic realm="Secure Area""
  • Received:
    undefined

❯ test/tests.ts:398:43


[failure] 398-398: test/presets/cloudflare-module.test.ts > nitro:preset:cloudflare-module > handles route rules - basic auth > rejects request with bad creds
AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality

  • Expected:
    "Basic realm="Secure Area""
  • Received:
    undefined

❯ test/tests.ts:398:43


[failure] 398-398: test/presets/bun.test.ts > nitro:preset:bun > handles route rules - basic auth > rejects request with bad creds
AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality

  • Expected:
    "Basic realm="Secure Area""
  • Received:
    undefined

❯ test/tests.ts:398:43


[failure] 398-398: test/presets/aws-lambda.test.ts > nitro:preset:aws-lambda-v1 > handles route rules - basic auth > rejects request with bad creds
AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality

  • Expected:
    "Basic realm="Secure Area""
  • Received:
    undefined

❯ test/tests.ts:398:43


[failure] 398-398: test/presets/aws-lambda.test.ts > nitro:preset:aws-lambda-v2 > handles route rules - basic auth > rejects request with bad creds
AssertionError: expected undefined to be 'Basic realm="Secure Area"' // Object.is equality

  • Expected:
    "Basic realm="Secure Area""
  • Received:
    undefined

❯ test/tests.ts:398:43

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tests.ts` at line 398, The 401 lacks the WWW-Authenticate header because
the route-rule middleware calling h3's requireBasicAuth is not propagating the
challenge headers from the requireBasicAuth result/exception; inspect the
middleware in src/runtime/internal/route-rules.ts (the function that forwards
options to requireBasicAuth at around the referenced call) and update it so when
requireBasicAuth returns/throws it copies any WWW-Authenticate header(s) into
the outgoing response (e.g., setHeader('WWW-Authenticate', value) on the
event/res) before returning/throwing, or rethrow the original h3 response object
so its headers are preserved; after that, re-run the test in test/tests.ts to
confirm expect(headers["www-authenticate"]) receives 'Basic realm="Secure
Area"'.

});

it("allows request with correct password", async () => {
const { status } = await callHandler({
url: "/rules/basic-auth/test",
headers: {
Authorization: "Basic " + btoa("admin:secret"),
},
});
expect(status).toBe(200);
});

it("disabled basic-auth for sub-rules", async () => {
const { status } = await callHandler({ url: "/rules/basic-auth/no-auth" });
expect(status).toBe(200);
});
});

it("handles route rules - allowing overriding", async () => {
const override = await callHandler({ url: "/rules/nested/override" });
expect(override.headers.location).toBe("/other");
Expand Down
Loading