Layer: L6 Presentation ยท Build flags: PC_ENABLE_FORWARDED_TRUST (requires PC_ENABLE_AUTH_LOCKOUT, which requires PC_ENABLE_AUTH)
What this example teaches
When the device sits behind a reverse proxy or load balancer, every request arrives from the proxy's single TCP address. A per-IP brute-force lockout keyed on that address is worthless: one abuser locks out every client at once, and distinct clients cannot be told apart.
PC_ENABLE_FORWARDED_TRUST fixes this by keying the auth lockout on the original client address the proxy reports in Forwarded (RFC 7239) or X-Forwarded-For - but only when the request's real TCP peer is a proxy you have explicitly trusted. That header is client-spoofable, so a direct, untrusted client's header is ignored: it can neither dodge its own lockout nor frame another address into one.
pc_forwarded_trust_add_cidr("192.0.2.0/24");
The accept-time throttle and the IP allowlist deliberately keep using the real TCP source (the proxy) - only the auth lockout follows the forwarded client.
Security model
- Empty trust table = trust nothing. Before you add a CIDR, no forwarded header is ever believed (the lockout keys on the real TCP peer, as without the flag).
- Untrusted peer -> header ignored. A client connecting directly (not via a trusted proxy) cannot set
X-Forwarded-For to escape its lockout or to lock out someone else.
- Malformed / obfuscated / unspecified token -> fall back to the TCP peer. An RFC 7239
for=unknown / for="_hidden", a non-address, or 0.0.0.0 is never used as a key.
Build and run
pio ci --board=esp32dev --project-option="framework=arduino" \
--project-option="build_flags=-DPC_ENABLE_AUTH_LOCKOUT=1 -DPC_ENABLE_FORWARDED_TRUST=1" \
--lib="." examples/L6-Presentation/ForwardedTrust/ForwardedTrust.ino
From a host inside the trusted CIDR (standing in for the proxy):
# lock out ONE client, keyed on its forwarded address:
curl -u admin:wrong -H 'X-Forwarded-For: 203.0.113.7' http://<ip>/secret # ...x THRESHOLD -> 429
# a DIFFERENT forwarded client is unaffected (per-client keying, not per-proxy):
curl -u admin:wrong -H 'X-Forwarded-For: 198.51.100.9' http://<ip>/secret # 401, not 429
# the right password clears that client's lockout:
curl -u admin:s3cret -H 'X-Forwarded-For: 203.0.113.7' http://<ip>/secret # 200
Annotated source
The complete sketch (ForwardedTrust.ino), reproduced verbatim with added explanatory comments:
#define PC_ENABLE_AUTH_LOCKOUT 1
#define PC_ENABLE_FORWARDED_TRUST 1
static const char *SSID = "YOUR_SSID";
static const char *PASSWORD = "YOUR_PASSWORD";
void setup()
{
delay(250);
pc_forwarded_trust_add_cidr("192.0.2.0/24");
[](uint8_t
id,
HttpReq *) { server.
send(
id, 200,
"text/plain",
"authenticated!"); },
"Restricted",
"admin",
"s3cret");
}
void loop()
{
}
Single-port HTTP server with deterministic, zero-allocation execution.
void send(uint8_t slot_id, int code, const char *content_type, const char *payload)
Send an HTTP response with a body and close the connection.
int32_t begin(const WebServerConfig *cfg=nullptr)
Initialize all connection slots and open all registered listeners.
void on(const char *path, HttpMethod method, Handler callback)
Register a route handler.
void handle()
Drive the server - call every Arduino loop() iteration.
Trusted-reverse-proxy resolution of a forwarded client address (PC_ENABLE_FORWARDED_TRUST).
bool init_wifi_physical(const char *, const char *)
Connect to a WiFi access point.
bool wifi_ready()
True if the WiFi station link is up (associated + an IP is assigned).
Layer 1 (Physical) - link bring-up and live egress-interface reporting.
Layer 7 (Application) - public HTTP routing API.
@ HTTP_GET
Safe, idempotent read.
Fully-parsed HTTP/1.1 request.