- former-api-specs/mes: Alarm-API, MoveIn-MoveOut-API, API-key authgaps (from ~/Desktop/mesapi) - former-api-specs/dnc: Delmia-Integration-API — Delmia document service + WW recipe-download notify (from ~/Desktop/delmiaintegration) - known-issues: inbound API compile error not client-visible; no api-method validate
6.7 KiB
WWSupport MES API — API key authentication: support & gaps
How the legacy WWSupport / APIServer ServiceStack service authenticates API-key callers, and
the API-key-specific gaps in it as of 2026-06-25 (the state of the ~/Desktop/mesapi copy).
Captured as reference for the ScadaBridge migration so the replacement Inbound API does not inherit
these weaknesses.
Scope: defensive review of an internally-owned legacy service — weaknesses and remediations, no exploit steps. Limited to API-key auth (other auth/config concerns are out of scope here).
Source files: APIServer/AppHost.cs, APIServer.ServiceInterface/MesServices.cs,
APIServer/App.config.
What's supported (API key)
-
ApiKeyAuthProvideris registered in the hostAuthFeature(AppHost.cs:63-70) withSessionCacheDuration = 30 minandRequireSecureConnection = false. -
Keys are DB-backed. Users, roles, and keys live in SQL via
OrmLiteAuthRepository(UseDistinctRoleTables = true);InitSchema()creates the tables at startup (AppHost.cs:53-59). There is no API-key config inApp.config— keys are issued/stored by the ServiceStack auth repository. -
Authorization. A valid key authenticates the request; the key's user must also hold the
MESAPIrole to reach any operation (MesServices.cs:6-8):[Authenticate] [RequiredRole("MESAPI")] public class MesServices : Service { ... } // movein, moveout, alarmstatus, simplealarmstatus -
Transports for the key (ServiceStack defaults, nothing overridden):
Authorization: Bearer <key>, HTTP Basic with the key as the username, or — sinceAllowInHttpParamsdefaults on —?apikey=<key>in the query string/form. -
Per-key environment tag. A key whose
Environment == "test"is routed to aTestDbconnection instead of production (AppHost.GetDbConnection,AppHost.cs:102-108). -
Transport. The listener is plain HTTP —
http://*:9501/(DEV) /http://*:9500/(QA/PROD) (App.config:57,68,80); no HTTPS listener is configured (relevant to gap #1).
Gaps & risks (worst first)
1. 🟠 High — the key is exposed in transit and in logs
Three settings compound:
- No TLS — plain-HTTP listeners (
App.config:57) andRequireSecureConnection = false(AppHost.cs:69), so the key crosses the network in cleartext to any on-path observer. - Key accepted in the URL —
AllowInHttpParams(default on) makes?apikey=<key>valid, so keys land in proxy logs and browser history. - Request logging persists it — the enabled
RequestLogsFeaturewrites request data to a CSV on disk (AppHost.cs:79-86), so a query-string key can be written to the log file.
Fix: terminate TLS and set RequireSecureConnection = true; set
ApiKeyAuthProvider { AllowInHttpParams = false } so keys must travel in the Authorization
header; redact auth fields from the request log and restrict the log file's ACLs.
2. 🟡 Medium — keys are not scoped to methods
A valid key + the MESAPI role grants access to every [RequiredRole("MESAPI")] endpoint
(move-in, move-out, both alarm reads). There is no per-key allow-list of methods, so one leaked
integration key exposes the entire MES surface.
Fix: scope keys per integration (separate roles/keys for read vs. move-in vs. move-out), the way the ScadaBridge Inbound API does (keys carry an explicit method allow-list).
3. 🟢 Low–Medium — the per-key test redirect is half-wired
GetDbConnection opens a "TestDb" connection for Environment == "test" keys
(AppHost.cs:104-106), but no TestDb connection string is defined in App.config — such
requests would throw. Separately, the move-out cycle-data read uses a raw
ConnectionStrings["BatchDB"] connection (see MoveIn-MoveOut-API.md), so it ignores the
redirect and reads production data even with a test key.
Fix: define TestDb (or remove the redirect), and route the raw cycle-data read through the
same environment-aware connection so a test key never touches production.
Remediation checklist (priority order)
- Enforce TLS +
RequireSecureConnection = true; stop accepting the key in the URL (AllowInHttpParams = false); redact/secure the request logs (#1). - Scope API keys per method/integration instead of one coarse
MESAPIrole (#2). - Fix or remove the
test→TestDbredirect and make the raw cycle-data read environment-aware (#3).
Contrast: ScadaBridge Inbound API
ScadaBridge uses an X-API-Key header on the data plane (POST /api/{method}), validated
server-side, with each key scoped to an explicit method allow-list — versus one coarse
MESAPI role granting all endpoints here. This file documents the legacy API-key posture so that
difference is intentional and verifiable during cutover.
Accepted auth transports
| Transport | mesapi (ServiceStack ApiKeyAuthProvider) |
ScadaBridge Inbound API |
|---|---|---|
Authorization: Bearer <key> |
✅ | ✅ — Bearer sbk_<keyId>_<secret> (the Bearer prefix is optional; a bare token in Authorization also works) |
Authorization: Basic <base64("<key>:")> (key as username) |
✅ | ❌ |
X-API-Key: <key> |
❌ (not supported by stock ServiceStack) | ✅ — raw token sbk_<keyId>_<secret> |
?apikey=<key> (query string / form param) |
✅ (AllowInHttpParams defaults on) |
❌ — headers only |
Session cookie after first auth (ss-id/ss-pid) |
✅ (30-min SessionCacheDuration) |
❌ — stateless; every request re-presents the key |
Evidence: mesapi is stock new ApiKeyAuthProvider(AppSettings) with no header customization (so
ServiceStack defaults apply); ScadaBridge logic is EndpointExtensions.cs:83-95.
Notes:
- The only common header is
Authorization: Bearer— the portable choice for a client that must talk to both. X-API-Keyis ScadaBridge-only;Basicand?apikey=are mesapi-only. Acurl -H "X-API-Key: …"authenticates ScadaBridge but is rejected by mesapi.- Precedence when two are sent: ScadaBridge —
Authorizationwins overX-API-Key; mesapi — ServiceStack checksAuthorization(Bearer/Basic) before theapikeyparam. - mesapi is looser, ScadaBridge is tighter: mesapi accepts the key in the URL and over a long-lived session cookie (more leak surface — see gap #1); ScadaBridge restricts to two headers, no URL param, no session.
- Token shape & verification: mesapi keys are opaque ServiceStack keys checked against the auth
repo; ScadaBridge keys are structured
sbk_<keyId>_<secret>verified by a peppered-HMAC constant-time compare and scoped to specific method names (case-sensitive).