V2.4.21
Hotfix for openSenseMap auth-token format. OSM's ingest endpoint wants the raw token in the Authorization header — NOT Bearer <token> as V2.3.16 shipped speculatively. Confirmed 2026-05-21 by direct A/B curl against a real auth-enabled box: raw token → HTTP 201 Created, Bearer <token> → HTTP 401 "Box access token not valid!".
Symptom
"Access token required" was enabled on a box on openSenseMap.org and the 64-char token pasted into /config → Save (no restart). Every subsequent OSM upload then failed with:
W (...) HTTP_CLIENT: This request requires authentication, but does not provide header information for that
E (...) HTTP_CLIENT: Error response
W (...) tx: openSenseMap perform error: ESP_ERR_NOT_SUPPORTED
W (...) tx: openSenseMap rc=-1 (retry 1/4)
(Madavi, sensor.community, aqi.eco, FTPS, MQTT — all other targets continued working normally.)
Why the firmware log message was misleading
The HTTP_CLIENT: This request requires authentication, but does not provide header information for that line is not "we forgot to send the header" — it's IDF's esp_http_client.c:1966 auth-retry handler logging: "I got a 401 back and looked for a WWW-Authenticate response header so I'd know which scheme to retry with (Basic / Digest), didn't find one, returning ESP_ERR_NOT_SUPPORTED". OSM's 401 response is a JSON body ({"code":"Unauthorized","message":"Box access token not valid!"}) with no WWW-Authenticate header, so IDF's auth-retry logic gives up with that misleading log. The firmware WAS sending the Bearer <token> header all along — it just was the wrong format for OSM.
Diagnosis path
Direct curl against https://ingress.opensensemap.org/boxes/<BOX_ID>/data?luftdaten=1 with the box's real token, comparing auth header formats:
Authorization: value |
Result |
|---|---|
Bearer <64-hex> (current firmware format) |
401 + "Box access token not valid!" |
<64-hex> (raw) |
422 → 201 (422 when sensor IDs don't match box's mapping; 201 when they do) |
X-ApiKey: <64-hex> (alternate header name) |
401 |
?accessToken=<64-hex> (query param) |
401 |
201 with raw token confirms OSM accepts it; 401 with Bearer confirms OSM rejects it. The 422 vs 401 split is the diagnostic that auth IS validated before body content checks — 422 means "auth OK, body unprocessable".
What changed
One-character-class change in main/transmission.c::send_osm():
- if (have_token) snprintf(authz, sizeof(authz), "Bearer %s", c->osm_access_token);
+ if (have_token) snprintf(authz, sizeof(authz), "%s", c->osm_access_token);Plus a fat WHY comment block above so the next reader doesn't re-add the Bearer prefix thinking it's "standard practice".
Backward compatibility
- Boxes WITHOUT auth required (every existing deployment until today): user leaves token field empty →
have_token == false→ noAuthorizationheader sent at all. Unchanged from V2.4.20. - Boxes WITH auth required: user pastes token into
/config→ raw token inAuthorizationheader → OSM accepts. Fixed by this release.
No checkbox or extra config field needed. The presence/absence of the token IS the gate.
Files touched
main/transmission.c(+15 LOC: 1-character format-string change, +13 lines of WHY comment)main/version.h: V2.4.20 → V2.4.21CHANGELOG.md: this entry
No partition change. No sdkconfig change. Drop-in OTA upgrade across all 4 board targets.