Skip to content

Comments

feat(route-rules): basic auth#4049

Open
RihanArfan wants to merge 4 commits intonitrojs:mainfrom
RihanArfan:feat/auth-route-rules
Open

feat(route-rules): basic auth#4049
RihanArfan wants to merge 4 commits intonitrojs:mainfrom
RihanArfan:feat/auth-route-rules

Conversation

@RihanArfan
Copy link
Member

🔗 Linked issue

Closes #3924

❓ Type of change

  • 📖 Documentation (updates to the documentation, readme, or JSdoc annotations)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

Adds support for configuring basic auth through route rules

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@RihanArfan RihanArfan requested a review from pi0 as a code owner February 21, 2026 02:16
@vercel
Copy link

vercel bot commented Feb 21, 2026

@RihanArfan is attempting to deploy a commit to the Nitro Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 21, 2026

📝 Walkthrough

Walkthrough

This PR introduces a new basic authentication route rule for Nitro, leveraging h3's built-in requireBasicAuth capability. It adds type definitions, a runtime handler, build configuration, test fixtures, and documentation for the feature.

Changes

Cohort / File(s) Summary
Type System & Core Implementation
src/types/route-rules.ts, src/runtime/internal/route-rules.ts
Adds NitroBasicAuthOptions interface with username, password, and realm fields; extends NitroRouteConfig with optional auth property; implements auth route rule handler that delegates to h3's requireBasicAuth.
Build Configuration
src/build/virtual/routing.ts
Registers "auth" in the RuntimeRouteRules constant, expanding recognized runtime route handlers from four to five options.
Tests & Fixtures
test/fixture/nitro.config.ts, test/tests.ts
Adds three route-specific auth configurations (with password only, with username and password, and disabled); introduces comprehensive test suite covering successful/failed authentication, credential validation, and disabled auth scenarios.
Documentation
docs/1.docs/5.routing.md
Introduces new Basic Authentication section with example routes demonstrating password-only and username+password configurations; expands Route Rules description to reference authentication.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(route-rules): basic auth' follows conventional commits format with type, scope, and subject.
Description check ✅ Passed The PR description clearly explains the feature: adding basic auth support through route rules, with documentation and tests included.
Linked Issues check ✅ Passed All coding requirements from issue #3924 are met: basic auth is now available as a route rule that can run before other middleware.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing basic auth as a route rule, with documentation and tests appropriately included.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 21, 2026

Open in StackBlitz

npm i https://pkg.pr.new/nitrojs/nitro@4049

commit: 23a8592

@vercel
Copy link

vercel bot commented Feb 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nitro.build Ready Ready Preview, Comment Feb 21, 2026 2:24am

Request Review

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/build/virtual/routing.ts (1)

92-106: ⚠️ Potential issue | 🟠 Major

auth route rule passwords are serialized in plaintext into the compiled bundle.

JSON.stringify(options) embeds the entire options object — including password — into the generated JavaScript at build time. Every consumer of the build artifact (minified or not) can read the credential in plaintext. This applies specifically to the auth rule because password is a secret, unlike headers or redirect targets.

Consider either:

  1. Filtering sensitive fields before serialization and resolving them at runtime via useRuntimeConfig, or
  2. Prominently documenting this limitation (the compiled bundle will contain the literal password string) and recommending that users pass process.env.SECRET_PASSWORD in their nitro.config.ts so the secret comes from the build environment rather than being committed to source.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/build/virtual/routing.ts` around lines 92 - 106, serializeRouteRule
currently embeds full options (including auth.password) into the compiled bundle
via JSON.stringify; update serializeRouteRule to strip or replace sensitive
fields for the "auth" rule before serialization (e.g., if name === "auth", clone
options and delete or replace the password field with a runtime reference token)
and ensure consumers resolve the actual secret at runtime (via useRuntimeConfig
or process.env) instead of emitting the literal password; keep existing behavior
for other rule names and preserve RuntimeRouteRules and handler emission
(__routeRules__, h._route, h._method) while serializing the sanitized options.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/1.docs/5.routing.md`:
- Around line 363-376: The example imports defineNitroConfig but calls
defineConfig — fix by making the symbols consistent: either import defineConfig
from "nitro" and leave the calls as defineConfig, or keep the import of
defineNitroConfig from "nitro/config" and rename the calls to defineNitroConfig;
update both basic-auth examples (the blocks that use
defineNitroConfig/defineConfig and the routeRules with auth) so the import and
the function call match (references: defineNitroConfig, defineConfig,
routeRules).
- Around line 359-401: Add a security note to the "Basic authentication" docs
warning that the `password` value is inlined verbatim into the compiled bundle
(because `serializeRouteRule` in src/build/virtual/routing.ts uses
JSON.stringify to embed route rules), and instruct users to supply secrets via
build-time environment variables (e.g., process.env.ADMIN_PASSWORD) while
explicitly warning that the resolved string will still appear in the generated
JS artifact; include guidance similar to other security-sensitive docs advising
against committing secrets to source and recommending runtime proxying or
external secret management if secrecy is required.

In `@src/types/route-rules.ts`:
- Line 34: Update the NitroBasicAuthOptions type to match h3's BasicAuthOptions
by adding optional realm?: string and validate?: (username: string, password:
string) => boolean | Promise<boolean>; then ensure wherever
NitroBasicAuthOptions is forwarded into requireBasicAuth (the call in
route-rules handling) accepts and passes these fields through unchanged so realm
customization and custom validation functions are preserved.

In `@test/tests.ts`:
- Around line 433-436: Add a new test that verifies child route rules can
override a parent auth rule: create overlapping fixtures analogous to the docs
(e.g., a parent rule matching "/rules/auth/**" that requires auth and a child
rule matching "/rules/auth/public/**" with auth: false), then use the existing
callHandler helper to request a child path like "/rules/auth/public/test" and
assert a 200 response (instead of the parent's 401). Place the test alongside
the existing "skips auth when set to false" test and reference the same
callHandler helper and route patterns to ensure the route-rule merger correctly
prefers the child auth: false override.

---

Outside diff comments:
In `@src/build/virtual/routing.ts`:
- Around line 92-106: serializeRouteRule currently embeds full options
(including auth.password) into the compiled bundle via JSON.stringify; update
serializeRouteRule to strip or replace sensitive fields for the "auth" rule
before serialization (e.g., if name === "auth", clone options and delete or
replace the password field with a runtime reference token) and ensure consumers
resolve the actual secret at runtime (via useRuntimeConfig or process.env)
instead of emitting the literal password; keep existing behavior for other rule
names and preserve RuntimeRouteRules and handler emission (__routeRules__,
h._route, h._method) while serializing the sanitized options.

Comment on lines +359 to 401
### Basic authentication

The `auth` route rule protects matching routes with [HTTP Basic Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Authentication). When a request hits a protected route, the browser prompts the user for credentials before granting access.

```ts [nitro.config.ts]
import { defineNitroConfig } from "nitro/config";

export default defineConfig({
routeRules: {
'/admin/**': {
auth: {
username: 'admin',
password: 'supersecret',
}
}
}
});
```

The `auth` option accepts the following properties:

| Property | Type | Required | Description |
| ---------- | -------- | -------- | ------------------------------------------------------------------ |
| `password` | `string` | Yes | Password for basic auth validation. |
| `username` | `string` | No | Username for basic auth validation. If omitted, any username is accepted. |
| `realm` | `string` | No | Realm for the basic auth challenge. Defaults to `""`. |

You can also explicitly disable authentication for a sub-route by setting `auth: false`:

```ts [nitro.config.ts]
import { defineNitroConfig } from "nitro/config";

export default defineConfig({
routeRules: {
'/admin/**': {
auth: { password: 'supersecret' }
},
'/admin/public/**': {
auth: false,
}
}
});
```
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

Document that the password is embedded verbatim in the compiled bundle.

serializeRouteRule in src/build/virtual/routing.ts calls JSON.stringify(options) to inline every route rule into the generated JavaScript, so password ends up in the build artifact in plaintext. Consider adding a note directing users to supply the value at build time via an environment variable (process.env.ADMIN_PASSWORD) and warning that the resolved string will still be present in the bundle, similar to the guidance in other security-sensitive integrations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/1.docs/5.routing.md` around lines 359 - 401, Add a security note to the
"Basic authentication" docs warning that the `password` value is inlined
verbatim into the compiled bundle (because `serializeRouteRule` in
src/build/virtual/routing.ts uses JSON.stringify to embed route rules), and
instruct users to supply secrets via build-time environment variables (e.g.,
process.env.ADMIN_PASSWORD) while explicitly warning that the resolved string
will still appear in the generated JS artifact; include guidance similar to
other security-sensitive docs advising against committing secrets to source and
recommending runtime proxying or external secret management if secrecy is
required.

Comment on lines +363 to +376
```ts [nitro.config.ts]
import { defineNitroConfig } from "nitro/config";

export default defineConfig({
routeRules: {
'/admin/**': {
auth: {
username: 'admin',
password: 'supersecret',
}
}
}
});
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

defineNitroConfig is imported but defineConfig is called — fix both new code examples.

Both new basic auth code blocks (and the pre-existing one at lines 339-357) import defineNitroConfig from "nitro/config" but call the undeclared defineConfig(). Either swap the import to defineConfig from "nitro" (matching the actual test fixture), or rename the calls to defineNitroConfig.

📝 Proposed fix (repeated in both new examples)
-import { defineNitroConfig } from "nitro/config";
+import { defineConfig } from "nitro";

-export default defineConfig({
+export default defineConfig({

or consistently:

-import { defineNitroConfig } from "nitro/config";
+import { defineNitroConfig } from "nitro/config";

-export default defineConfig({
+export default defineNitroConfig({

Also applies to: 388-401

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/1.docs/5.routing.md` around lines 363 - 376, The example imports
defineNitroConfig but calls defineConfig — fix by making the symbols consistent:
either import defineConfig from "nitro" and leave the calls as defineConfig, or
keep the import of defineNitroConfig from "nitro/config" and rename the calls to
defineNitroConfig; update both basic-auth examples (the blocks that use
defineNitroConfig/defineConfig and the routeRules with auth) so the import and
the function call match (references: defineNitroConfig, defineConfig,
routeRules).

prerender?: boolean;
proxy?: string | ({ to: string } & ProxyOptions);
isr?: number /* expiration */ | boolean | VercelISRConfig;
auth?: NitroBasicAuthOptions | false;
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, locate and examine the NitroBasicAuthOptions definition
rg -n "NitroBasicAuthOptions" --type ts -A 10 -B 2

Repository: nitrojs/nitro

Length of output: 1371


🏁 Script executed:

# Check h3 version and look for BasicAuth types in the installed package
cat package.json | grep -A 5 -B 5 "h3"

Repository: nitrojs/nitro

Length of output: 1034


🏁 Script executed:

# Search for requireBasicAuth in the codebase or node_modules
find . -path ./node_modules -prune -o -type f -name "*.ts" -o -name "*.d.ts" | xargs rg -l "requireBasicAuth" 2>/dev/null | head -20

Repository: nitrojs/nitro

Length of output: 94


🏁 Script executed:

# Look for h3 package types and requireBasicAuth signature
find . -path ./node_modules/h3 -type f \( -name "*.d.ts" -o -name "*.ts" \) | head -20

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check how the auth field from NitroBasicAuthOptions is used with requireBasicAuth
rg -n "requireBasicAuth|basicAuth" --type ts -B 3 -A 3 src/

Repository: nitrojs/nitro

Length of output: 789


🏁 Script executed:

# Look directly in node_modules h3 for the requireBasicAuth type definition
cat node_modules/h3/dist/index.d.ts 2>/dev/null | grep -A 10 "requireBasicAuth"

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Try to find h3 types in alternative locations
ls -la node_modules/h3/dist/ 2>/dev/null | head -20

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check node_modules structure for h3
find node_modules -type d -name "h3" 2>/dev/null

Repository: nitrojs/nitro

Length of output: 39


🌐 Web query:

h3 requireBasicAuth 2.0.1-rc.14 TypeScript signature options shape

💡 Result:

In h3 2.0.1-rc.14, the function is documented as:

requireBasicAuth(event, opts)

where opts is BasicAuthOptions. (h3.dev)

The published typings for this version point TypeScript at:

So, in TS, the “options shape” you’re looking for is exactly:

type RequireBasicAuthOptions = BasicAuthOptions;
type RequireBasicAuthSignature = typeof requireBasicAuth;

and you can reveal it in-editor with:

import { requireBasicAuth } from "h3";

type Sig = typeof requireBasicAuth;
type Opts = Parameters<typeof requireBasicAuth>[1]; // BasicAuthOptions
``` ([app.unpkg.com](https://app.unpkg.com/h3%402.0.1-rc.14/files/package.json))

Citations:

- 1: https://www.h3.dev/utils/security
- 2: https://app.unpkg.com/h3%402.0.1-rc.14/files/package.json
- 3: https://app.unpkg.com/h3%402.0.1-rc.14/files/package.json

---

🌐 Web query:

`h3 BasicAuthOptions type definition 2.0.1-rc.14 github`

💡 Result:

For **h3@2.0.1-rc.14**, the package’s `package.json` declares its TypeScript types entry as:

- `types: "./dist/_entries/generic.d.mts"` and exports runtime entrypoints under `./dist/_entries/*` (including `generic.mjs`). [1]

So the **`BasicAuthOptions`** type definition you’re looking for should be in that bundled types file (or one of the files it references), i.e.:

- `dist/_entries/generic.d.mts` (in the **h3@2.0.1-rc.14** package contents). [1]

I wasn’t able to fetch that `.d.mts` file content via the browsing tool here (only the metadata page was accessible), so I can’t paste the exact `type`/`interface` text for `BasicAuthOptions`. If you open the types entry locally, you can jump straight to it with:

```bash
node -p "require('h3/package.json').types"
# then open that file and search for: BasicAuthOptions

Sources:
[1] h3@2.0.1-rc.14 package.json on unpkg (shows types path and exports). (unpkg.com)

Citations:


🏁 Script executed:

# Try to access the h3 package type definitions through npm or git
curl -s https://unpkg.com/h3@2.0.1-rc.14/dist/_entries/generic.d.mts 2>/dev/null | grep -A 10 "BasicAuthOptions"

Repository: nitrojs/nitro

Length of output: 5941


🌐 Web query:

h3 github BasicAuthOptions interface definition source code

💡 Result:

In h3 (v2) the BasicAuthOptions type is defined like this (from the published typings generated from the h3js/h3 repo):

type _BasicAuthOptions = {
  /** Validate username for basic auth. */
  username: string;
  /** Simple password for basic auth. */
  password: string;
  /** Custom validation function for basic auth. */
  validate: (username: string, password: string) => boolean | Promise<boolean>;
  /**
   * Realm for the basic auth challenge.
   * Defaults to "auth".
   */
  realm: string;
};

type BasicAuthOptions =
  Partial<_BasicAuthOptions> &
    ({ validate: _BasicAuthOptions["validate"] } | { password: _BasicAuthOptions["password"] });

Source (file): dist/h32.d.mts in the h3 package build output. [1] (Repo: h3js/h3.) [2]

References: [1] (app.unpkg.com) [2] (github.com)

Citations:


🏁 Script executed:

# Try to extract h3 full types from unpkg with more specific content request
python3 << 'EOF'
import urllib.request
import re

url = "https://unpkg.com/h3@2.0.1-rc.14/dist/_entries/generic.d.mts"
try:
    with urllib.request.urlopen(url, timeout=5) as response:
        content = response.read().decode('utf-8')
        # Search for BasicAuthOptions definition
        match = re.search(r'(interface|type)\s+BasicAuthOptions\s*[{=][^}]*}', content, re.DOTALL)
        if match:
            print("Found BasicAuthOptions:")
            print(match.group(0)[:500])
        else:
            # Try to find it in a different format
            if 'BasicAuthOptions' in content:
                idx = content.find('BasicAuthOptions')
                print("BasicAuthOptions context:")
                print(content[max(0, idx-100):min(len(content), idx+300)])
            else:
                print("BasicAuthOptions not found in file")
except Exception as e:
    print(f"Error: {e}")
EOF

Repository: nitrojs/nitro

Length of output: 191


Update NitroBasicAuthOptions to include realm and validate fields from h3's BasicAuthOptions.

h3's BasicAuthOptions in version 2.0.1-rc.14 includes realm?: string (defaults to "auth") and an optional validate?: (username: string, password: string) => boolean | Promise<boolean> for custom authentication logic. NitroBasicAuthOptions is currently missing both fields. Passing incomplete options to requireBasicAuth at line 89 of src/runtime/internal/route-rules.ts will silently drop support for realm customization and user-defined validation functions, causing incorrect behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types/route-rules.ts` at line 34, Update the NitroBasicAuthOptions type
to match h3's BasicAuthOptions by adding optional realm?: string and validate?:
(username: string, password: string) => boolean | Promise<boolean>; then ensure
wherever NitroBasicAuthOptions is forwarded into requireBasicAuth (the call in
route-rules handling) accepts and passes these fields through unchanged so realm
customization and custom validation functions are preserved.

Comment on lines +433 to +436
it("skips auth when set to false", async () => {
const { status } = await callHandler({ url: "/rules/no-auth/test" });
expect(status).toBe(200);
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

auth: false test only covers a standalone route — the documented child-overrides-parent scenario is untested.

The docs explicitly show /admin/** with auth and /admin/public/** with auth: false as the use-case for this feature, but the test fixture tests an entirely separate /rules/no-auth/** route that never had a parent auth rule. If the route-rule merger doesn't correctly handle the case where both a parent (auth: {...}) and a child (auth: false) match simultaneously, the 401 from the parent handler would fire before the child's auth: false guard could take effect. Adding an overlapping fixture pair (e.g., /rules/auth/** + /rules/auth/public/** with auth: false) would validate this scenario.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tests.ts` around lines 433 - 436, Add a new test that verifies child
route rules can override a parent auth rule: create overlapping fixtures
analogous to the docs (e.g., a parent rule matching "/rules/auth/**" that
requires auth and a child rule matching "/rules/auth/public/**" with auth:
false), then use the existing callHandler helper to request a child path like
"/rules/auth/public/test" and assert a 200 response (instead of the parent's
401). Place the test alongside the existing "skips auth when set to false" test
and reference the same callHandler helper and route patterns to ensure the
route-rule merger correctly prefers the child auth: false override.

@RihanArfan
Copy link
Member Author

Will push fixes for those soon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New Route Rule: Basic Auth

1 participant