# Pi-Web HTTP Basic Auth Security Model: How Remote Access Protection Works

> Secure remote access to Pi-Web with its two-layer security model, combining host-trust validation and HTTP Basic Authentication. Learn how it works.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: deep-dive
- Published: 2026-08-13

---

**Pi-Web uses a two-layer security model that combines host-trust validation with optional HTTP Basic Authentication, where the username is hard-coded to "pi" and the password is set via the `PI_WEB_PASSWORD` environment variable.**

The pi-web repository (agegr/pi-web) implements a lightweight but robust security layer for all incoming HTTP requests. Whether accessing the web UI or calling API endpoints, remote clients must pass both host-based trust checks and—when configured—HTTP Basic Auth validation. This article breaks down the complete security flow implemented in the source code.

## Layer 1: Host-Trust Validation

Before any authentication occurs, the `proxy` middleware validates whether the requesting host is trusted. This happens in **[[`lib/request-security.ts`](https://github.com/agegr/pi-web/blob/main/lib/request-security.ts)](https://github.com/agegr/pi-web/blob/main/lib/request-security.ts)**.

The trust logic splits traffic into two categories:

- **API routes**: Allowed only from hosts passing `isApiRequestAllowed`
- **Non-API routes (UI pages)**: Allowed only if the host satisfies `isApiRequestHostAllowed`

Untrusted requests receive an immediate **403 Forbidden** response. This host-gating prevents unauthorized network scanning from reaching the authentication layer entirely.

## Layer 2: Password Protection via HTTP Basic Auth

When `PI_WEB_PASSWORD` is defined and non-empty, pi-web enables password protection. The password gate is controlled by `isWebPasswordEnabled` in **[[`lib/web-auth.ts`](https://github.com/agegr/pi-web/blob/main/lib/web-auth.ts)](https://github.com/agegr/pi-web/blob/main/lib/web-auth.ts)**.

### Authentication Requirements

Every request must include a valid `Authorization: Basic …` header with these constraints:

| Component | Value |
|-----------|-------|
| Username | Hard-coded to `"pi"` (constant `PI_WEB_AUTH_USERNAME`) |
| Password | Must exactly match `PI_WEB_PASSWORD` environment variable |
| Header format | `Basic <base64-encoded-credentials>` |

### Secure Validation with Timing-Safe Comparison

The `isValidBasicAuthorization` function in **[`web-auth.ts`](https://github.com/agegr/pi-web/blob/main/web-auth.ts)** performs validation:

```typescript
// Pseudocode based on source implementation
function isValidBasicAuthorization(header: string): boolean {
  // 1. Parse "Basic <base64>" format
  // 2. Safely decode Base64 payload
  // 3. Split into username:password
  // 4. Compare using crypto.timingSafeEqual on SHA-256 hashes
}

```

Malformed payloads are rejected. Valid credentials are compared using **`crypto.timingSafeEqual`** on SHA-256 hashes, eliminating timing-attack vulnerabilities.

## Authentication Failure Responses

Pi-web returns distinct HTTP status codes for different failure modes:

- **403 Forbidden**: Host failed trust validation (blocked before auth)
- **401 Unauthorized**: Password required but missing or invalid, with header:
  ```

  WWW-Authenticate: Basic realm="Pi Web", charset="UTF-8"
  ```

This header prompts browsers to display a native login dialog.

## Enabling and Using HTTP Basic Auth

### Configure Password Protection

```bash

# In your environment or .env file

export PI_WEB_PASSWORD="s3cr3tP@ss"

# Start the server

npm run dev

```

When `PI_WEB_PASSWORD` is unset or empty, pi-web runs without authentication—useful for local development.

### Authenticated Requests with curl

Access the UI:

```bash
curl -u pi:s3cr3tP@ss http://localhost:30141/

```

Call an API endpoint:

```bash
curl -u pi:s3cr3tP@ss http://localhost:30141/api/sessions

```

The `-u` flag automatically encodes credentials into the required `Authorization: Basic …` header.

## Key Source Files

| File | Security Role |
|------|---------------|
| [[`lib/web-auth.ts`](https://github.com/agegr/pi-web/blob/main/lib/web-auth.ts)](https://github.com/agegr/pi-web/blob/main/lib/web-auth.ts) | Username/password constants, password-enabled check, timing-safe validation |
| [[`proxy.ts`](https://github.com/agegr/pi-web/blob/main/proxy.ts)](https://github.com/agegr/pi-web/blob/main/proxy.ts) | Middleware orchestrating trust checks, password gate, and 401/403 responses |
| [[`lib/request-security.ts`](https://github.com/agegr/pi-web/blob/main/lib/request-security.ts)](https://github.com/agegr/pi-web/blob/main/lib/request-security.ts) | Host-trust definitions: `isApiRequestAllowed`, `isApiRequestHostAllowed` |

## Summary

- **Pi-web HTTP Basic Auth security model** combines host-trust validation with optional password protection
- Host trust is enforced first; untrusted clients receive 403 before reaching authentication
- Password protection activates when `PI_WEB_PASSWORD` is set; username is always `"pi"`
- Credentials are validated with `crypto.timingSafeEqual` on SHA-256 hashes to prevent timing attacks
- Failed authentication returns 401 with `WWW-Authenticate` header prompting credential entry
- Three files implement the complete security stack: [`request-security.ts`](https://github.com/agegr/pi-web/blob/main/request-security.ts), [`web-auth.ts`](https://github.com/agegr/pi-web/blob/main/web-auth.ts), and [`proxy.ts`](https://github.com/agegr/pi-web/blob/main/proxy.ts)

## Frequently Asked Questions

### What happens if I don't set `PI_WEB_PASSWORD`?

Pi-web runs without authentication. All requests from trusted hosts proceed directly to the application. This mode is intended for local development only.

### Can I change the username from "pi" to something else?

No. The username `"pi"` is hard-coded as the constant `PI_WEB_AUTH_USERNAME` in [`lib/web-auth.ts`](https://github.com/agegr/pi-web/blob/main/lib/web-auth.ts). Only the password is configurable.

### Why does pi-web use SHA-256 with timingSafeEqual instead of direct string comparison?

Direct string comparison (`===`) short-circuits on first mismatch, leaking timing information that attackers can exploit. SHA-256 hashing ensures both values have identical length, and `crypto.timingSafeEqual` performs a constant-time comparison regardless of where differences occur.

### How do I troubleshoot 403 vs 401 errors?

A **403 Forbidden** indicates host-trust failure—check [`lib/request-security.ts`](https://github.com/agegr/pi-web/blob/main/lib/request-security.ts) for your client's IP or origin. A **401 Unauthorized** means you reached the authentication layer but provided missing or invalid credentials; verify your `PI_WEB_PASSWORD` value and header encoding.