How Basic Authentication Works with PI_WEB_PASSWORD and Allowed Hosts in Pi-Web

Pi-web activates HTTP Basic Authentication when the PIWEBPASSWORD environment variable is set, optionally restricting access to specific hosts via PIWEBALLOWED_HOSTS.

The agegr/pi-web repository implements a lightweight, environment-driven authentication layer that protects the Next.js UI and API without modifying application code. This guide explains the complete flow from environment configuration to request validation, referencing the actual source implementation.


How Authentication Is Triggered

Authentication is gated entirely by environment variables. The server startup script checks for PIWEBPASSWORD and, if present, injects a middleware that intercepts all incoming requests.

Variable Required Purpose
PIWEBPASSWORD Yes (to enable auth) The clear-text password clients must provide via Authorization: Basic … header
PIWEBALLOWED_HOSTS No Comma-separated list of permitted hostnames/IPs; requests from other hosts receive 401 Unauthorized even with valid credentials

If PIWEBPASSWORD is unset, the middleware is skipped entirely and the UI runs without authentication.


Where Authentication Lives: bin/pi-web.js

The core logic resides in bin/pi-web.js, the server bootstrap file. This script:

  1. Creates an Express-compatible middleware stack
  2. Conditionally registers the authentication middleware based on process.env.PIWEBPASSWORD
  3. Falls through to the Next.js request handler after successful validation
// bin/pi-web.js — simplified structure showing middleware registration
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  createServer((req, res) => {
    const parsedUrl = parse(req.url, true);
    
    // Authentication middleware injected here when PIWEBPASSWORD is set
    if (process.env.PIWEBPASSWORD) {
      const allowedHosts = (process.env.PIWEBALLOWED_HOSTS || '')
        .split(',')
        .map(h => h.trim())
        .filter(Boolean);
      
      const host = req.headers.host?.split(':')[0] ?? '';
      if (allowedHosts.length && !allowedHosts.includes(host)) {
        res.statusCode = 401;
        res.end('Host not allowed');
        return;
      }

      const auth = req.headers.authorization ?? '';
      const match = auth.match(/^Basic\s+(.+)$/i);
      if (!match) {
        res.statusCode = 401;
        res.setHeader('WWW-Authenticate', 'Basic realm="Pi-Web"');
        res.end('Authentication required');
        return;
      }

      const decoded = Buffer.from(match[1], 'base64').toString('utf8');
      const [, password] = decoded.split(':'); // username ignored
      
      if (password !== process.env.PIWEBPASSWORD) {
        res.statusCode = 401;
        res.setHeader('WWW-Authenticate', 'Basic realm="Pi-Web"');
        res.end('Invalid credentials');
        return;
      }
    }

    handle(req, res, parsedUrl);
  }).listen(30141, (err) => {
    if (err) throw err;
    console.log('> Ready on http://localhost:30141');
  });
});

Step-by-Step Request Flow

Understanding how a request moves through the authentication layer helps debug access issues.

Step 1: Host Validation

When PIWEBALLOWED_HOSTS is defined, the middleware extracts the Host header, strips any port suffix, and validates against the allowlist.

// Host normalization: "localhost:30141" → "localhost"
const host = req.headers.host?.split(':')[0] ?? '';

Mismatch behavior: Immediate 401 response with body Host not allowed. No password prompt is issued.

Step 2: Basic Auth Header Parsing

Valid hosts proceed to password verification. The middleware expects:


Authorization: Basic <base64>

Where <base64> encodes username:password. The username is discarded — only the password segment matters.

Step 3: Password Comparison

The decoded password is compared to PIWEBPASSWORD using strict equality (case-sensitive). There is no hashing; the password is stored and compared in plaintext.

Success: Request continues to handle(req, res, parsedUrl), entering Next.js routing.
Failure: 401 response with WWW-Authenticate: Basic realm="Pi-Web" header, triggering browser credential dialogs.


Practical Configuration Examples

Enable authentication with a single environment variable:


# Unix/Linux

export PIWEBPASSWORD=superSecret123
npm start

Restrict to specific hosts:

export PIWEBPASSWORD=superSecret123
export PIWEBALLOWED_HOSTS=localhost,192.168.1.100,pi-web.mydomain.com
npm start

Docker deployment:


# Dockerfile excerpt

ENV PIWEBPASSWORD=${PIWEB_PASSWORD}
ENV PIWEBALLOWED_HOSTS=${PIWEB_ALLOWED_HOSTS}
CMD ["node", "bin/pi-web.js"]

Making Authenticated Requests

Browser access triggers the native credential dialog. Programmatic access requires proper header construction.

Using curl

Empty username (password-only):

curl -u :superSecret123 http://localhost:30141/api/sessions

With explicit header:

curl -H "Authorization: Basic $(echo -n ':superSecret123' | base64)" \
  http://localhost:30141/api/sessions

Using JavaScript (fetch)

const password = 'superSecret123';
const credentials = btoa(`:${password}`);

const response = await fetch('http://localhost:30141/api/sessions', {
  headers: {
    'Authorization': `Basic ${credentials}`
  }
});

Testing Host Restrictions

From an unauthorized host:


# Machine at 10.0.0.5 attempts access

curl http://10.0.0.5:30141/api/sessions

# → HTTP/1.1 401 Unauthorized

# → Body: "Host not allowed"

From an authorized host with wrong password:

curl -u :wrongPassword http://localhost:30141/api/sessions

# → HTTP/1.1 401 Unauthorized

# → WWW-Authenticate: Basic realm="Pi-Web"

Integration with Next.js Architecture

The authentication layer sits outside the Next.js application code. This design provides several benefits:

  • Zero application changes: Routes in app/api/... and pages in app/... require no auth logic
  • Universal protection: Static assets, API endpoints, and server-side rendered pages are all covered
  • Environment portability: Same codebase runs with or without authentication based on deployment context

Related authentication files (for alternative methods):

File Purpose
app/api/auth/login/[provider]/route.ts OAuth-based login flows
app/api/auth/api-key/[provider]/route.ts API key generation and validation
next.config.ts Server-side environment variable exposure

Basic auth operates independently of these OAuth/API-key systems. They can coexist: basic auth protects the UI entry point, while API keys authenticate programmatic API access.


Security Considerations

Based on the agegr/pi-web implementation:

  • Plaintext storage: PIWEBPASSWORD is compared directly without hashing. Protect the environment variable accordingly.
  • No rate limiting: Failed attempts are not throttled; consider network-level protection for exposed deployments.
  • HTTP by default: The development server runs unencrypted. Use a reverse proxy (nginx, traefik) with TLS for production.
  • Host header trust: PIWEBALLOWED_HOSTS relies on the Host header, which can be manipulated by clients. Use it for convenience, not as a security boundary.

Summary

  • Enable auth: Set PIWEBPASSWORD environment variable
  • Restrict hosts: Optionally add PIWEBALLOWED_HOSTS comma-separated list
  • Entry point: bin/pi-web.js registers the middleware before Next.js handling
  • Header format: Authorization: Basic <base64(:password)>
  • Username ignored: Only password segment is validated
  • Fails closed: Missing/invalid credentials yield 401 with WWW-Authenticate header

Frequently Asked Questions

How do I disable authentication entirely?

Unset PIWEBPASSWORD or remove it from your environment. The middleware is conditionally registered only when this variable exists.

Why is my password not working with special characters?

Ensure proper URL-encoding or shell-escaping when setting PIWEBPASSWORD. The comparison is literal — special characters must match exactly, including case.

Can I use PIWEBALLOWED_HOSTS without setting a password?

No. The host validation logic exists inside the password-guarded middleware block. Without PIWEBPASSWORD, the entire authentication layer is bypassed.

Does basic auth protect WebSocket connections?

The middleware in bin/pi-web.js wraps the HTTP request handler. WebSocket upgrade requests first pass through this handler, so yes — unauthenticated WebSocket upgrade attempts are rejected with 401.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →