Contract — Auth Headers and Error Responses

Feature: 057 Close Increment 3 (P3)

1. Bearer token

Every authenticated endpoint expects:

Authorization: Bearer <token>

<token> is one of:

  • A HAP-issued JWT (validated via JWKS from WBSP_HAP_ISSUER).
  • A wbsp-api-issued CI JWT (validated against the local Ed25519 public key, rejected if jti is not present in the users store).

Both produce the same Identity shape to downstream middleware:

Identity {
  Sub       string   // HAP sub, or impersonated sub for CI tokens
  Email     string   // for log/display
  IsAdmin   bool     // from users.json at request time
  TenantSet []string // from users.json at request time (live lookup)
  TokenID   string   // jti for CI tokens; HAP token's jti or "" for HAP
  Source    string   // "hap" | "ci"
}

2. 401 responses

The response body always contains an error field naming the failure mode:

{ "error": "missing Authorization header" }
{ "error": "malformed Bearer token" }
{ "error": "expired token" }
{ "error": "invalid signature" }
{ "error": "revoked CI token" }
{ "error": "unknown issuer" }

The CLI uses these messages to decide whether to attempt a re-login (token expired / revoked) vs. surface the failure (unknown issuer / malformed).

3. 403 responses

{ "error": "no tenants mapped to your account" }
{ "error": "you are not authorised to act on tenant <name>" }
{ "error": "admin required" }
{ "error": "tenant <name> not found" }

4. 409 responses (concurrency / invariant)

{ "error": "cannot revoke admin: would leave zero admins" }

This is the only 409 — all other validation issues are 400.

5. CSRF / CORS

  • No CSRF tokens: the API is bearer-authenticated; cookies are not used for auth so CSRF is not applicable.
  • CORS: locked down by default; only the wbsp-platform UI origin (when one exists, future feature) is in the allow-list. Today no browser-side caller exists.

6. TLS

The API is reached via the platform's Traefik IngressRoute, which terminates TLS on the ALB+ACM front. wbsp-api itself listens on plain HTTP inside the cluster. No new cert wiring.