Skip to content

Add authentication to the web UI / HTTP control plane #52

Description

@marklynch

Problem

The HTTP server (port 80, web_handlers.c) currently requires no authentication. Anyone with LAN access can, unauthenticated:

  • Flash arbitrary firmware via POST /update (full device takeover)
  • Change WiFi credentials (/provision) and MQTT broker/creds (/mqtt_config)
  • Reboot the device (each config POST calls esp_restart())
  • Inject arbitrary frames onto the pool bus via POST /api/test_decode

All POST endpoints are also CSRF-able (no token, no Origin/Referer check), so a malicious page the owner visits could drive these against the device's known IP.

This issue tracks adding auth to the HTTP control plane. (TCP bridge on 7373 is tracked separately — it needs its own scheme and depends on this landing first.)

Authentication mechanisms available

ESP-IDF's esp_http_server has no built-in auth — it's enforced in handlers (typically a wrapper run before the real handler).

Mechanism How it works Notes
HTTP Basic Authorization: Basic base64(user:pass); decode + compare, 401 + WWW-Authenticate if missing Smallest change (~40 lines), native browser dialog, zero client code. Credentials are base64 (not encrypted) → needs HTTPS or accept LAN-only exposure.
HTTP Digest Challenge/response, password not sent in clear MD5-based, no IDF helper, fiddly nonce state — poor effort/value. Skip.
Form + session cookie Login page → random token (esp_random()), Set-Cookie: HttpOnly, validate per request from in-RAM session table Nicer UX (logout/expiry), more code + RAM state. Still wants HTTPS.
API token / Bearer Pre-shared token in a header Good for automation/scripts, not browser-friendly. Could complement Basic.
TLS client certs Mutual TLS Overkill / unusable for a household web UI.

Transit protection (any of the above) means moving from httpd to esp_https_server (same API, TLS via mbedTLS, server cert in NVS/flash).

Suggested default: HTTP Basic now (gates every mutating endpoint + /update), HTTPS as a follow-up.

Setting the initial password (bootstrap)

Principle: never ship a fixed default password (same mistake as the hardcoded poolsetup SoftAP password).

Recommended: set the admin password during WiFi provisioning, in the existing SoftAP captive portal.

  • During first-run the device is only on its own WPA2 SoftAP — that AP is the trusted bootstrap channel.
  • Add an "Admin password" field to the /wifi provisioning form.
  • On save, store a salted hash in NVS (same pattern as wifi_credentials_save / mqtt_save_config), reboot into station mode.
  • Once on the real LAN, require auth on HTTP endpoints. The only no-password window is while physically connected to the device's own AP.

Alternatives considered: per-device random password (awkward for a board with no sticker/screen), trust-on-first-use (loses to whoever connects first), known-default + forced change (still has a guessable window).

Storage & verification

  • Store a salted hash (mbedTLS PBKDF2 or salted SHA-256), never plaintext.
  • Constant-time comparison.
  • Ideally enable flash encryption so the NVS blob isn't readable off-chip (ties in with the Secure Boot / signed-OTA work).

Reset path

Forgotten-password recovery must be physical: add a factory-reset trigger (long-press boot/GPIO button, or N rapid power cycles) that clears the admin hash + WiFi creds and drops back into provisioning. Also the "bought it secondhand" story.

Key decisions to make

  1. Mechanism: Basic vs session-cookie? (recommend Basic to start)
  2. Transport: plain HTTP on LAN, or move to esp_https_server (HTTPS)?
  3. Initial password: set during provisioning (recommended) vs per-device random vs other?
  4. Hashing: PBKDF2 vs salted SHA-256; iteration count.
  5. Flash encryption: enable now or defer?
  6. Factory reset: which physical trigger (button long-press vs power-cycle count)?
  7. CSRF: rely on auth + Origin/Referer checks, or add tokens to POST forms?
  8. Scope: auth all endpoints, or leave read-only /status open?

Scope / dependencies

  • This is a prerequisite for the TCP bridge (7373) hardening — the bridge token must be displayed on an authenticated config page.
  • Does not replace signed OTA (signing ensures only your images run; auth/anti-rollback stops unauthorized triggering and downgrade).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions