EP-001: Authentication & Access Control — Phase 1 Hardening
Phase 1 hardening sprint, 2026-04. All work committed to feature branches in
hmp-auth-serviceandhmp-shared-packages. Not yet pushed.
This epic gained two structural improvements during Phase 1: WebSocket JWT validation became real (it was previously a TODO marker on the gateways) and a service-account JWT mechanism was introduced so backend services can authenticate to each other without forwarding user tokens.
C1 — WebSocket Gateway JWT Authentication
Branch: feat/ws-jwt-auth (in hmp-housekeeping-service, hmp-pos-service,
hmp-web-dashboard, hmp-mobile-apps)
What was fixed
HousekeepingGatewayandKitchenGatewaynow validate JWTs onhandleConnection. Connections with missing/invalid/expired tokens are immediately disconnected.- Token extraction supports three sources: Socket.IO
auth.token(mobile), query string (web), andAuthorizationheader (server-to-server). joinPropertyandjoinFloor/joinOutletevents verify the requested propertyId matches the JWT claim. Cross-property join attempts return an error and do not subscribe the socket to the target room.- Mobile clients in all four React Native apps (HK, manager, F&B, guest) updated to forward the current access token from the auth store on every reconnect.
Files touched
hmp-housekeeping-service/src/gateways/housekeeping.gateway.ts(+.spec.ts)hmp-pos-service/src/kitchen/kitchen.gateway.ts(+.spec.ts)hmp-web-dashboard/src/hooks/useRealtimeUpdates.tshmp-mobile-apps/packages/shared-realtime/src/useRealtimeTasks.tshmp-mobile-apps/packages/shared-realtime/src/useRealtimeKitchen.ts
Tests
22+ new tests across the gateway specs, all passing. Coverage matrix:
- valid token → connection accepted
- expired token → disconnected
- malformed token → disconnected
- missing token → disconnected
joinPropertywith matching propertyId → joins roomjoinPropertywith mismatched propertyId → returns error, no joinjoinFloor/joinOutletwith valid scope → joins narrower room- mobile hook reconnects with refreshed token
Open follow-ups
- HS256 only. Both gateways use
process.env.JWT_SECRETdirectly. RS256 with per-service public keys is required before production. Tracked inproject_phase2-phase3-scope.md. - No connection-level rate limiting. A misbehaving client could open thousands of sockets. Phase 2 should add a per-tenant connection cap.
C4a — Internal Service-Account JWT Mechanism
Branch: feat/service-account-jwt (in hmp-shared-packages,
hmp-reservation-service, hmp-infrastructure)
What was added
JwtAuthGuardin@hmp/authnow distinguishes service-account tokens (scope: 'service',aud: 'hmp-internal') from user tokens (scope: 'user',aud: 'hmp-public'). Service tokens getreq.user.type = 'service'andtenantId/propertyIdfrom headers; user tokens get them from claims.BaseServiceClientin@hmp/service-clientsauto-injectsAuthorization: Bearer <INTERNAL_SERVICE_TOKEN>and forwardsx-tenant-id,x-property-id,x-request-idfromAsyncLocalStorage.- New script
hmp-infrastructure/scripts/generate-service-token.shfor ops to mint and rotate service-account tokens.
Architecture documentation
Full spec in architecture/internal-service-auth.md.
Companion docs:
Tests
18 new tests on JwtAuthGuard (token type discrimination, header forwarding, expiry,
audience mismatch). All passing.
Open follow-ups
- HS256 → RS256 migration (same as C1 — shared secret blast radius).
- Per-service scopes. Today all service tokens carry
scope: 'service'. Phase 2 should introduceservice:reservation,service:property, etc., for finer-grained revocation. - Token storage. Demo uses static env value; staging/prod should pull from External Secrets Operator → AWS Secrets Manager.
Summary
| Story | Status before | Status after | Notes |
|---|---|---|---|
| US-001-001 (User Login) | Built | Built | No change |
| US-001-002 (JWT Refresh) | Built | Built | Now coexists cleanly with service-account tokens |
| US-001-006 (RBAC) | Built | Built | Service tokens bypass RBAC; receivers must enforce ownership |
| US-001-010 (Brute force) | Built | Built | No change |
| (new) WebSocket auth | not tracked | shipped | New scope, see C1 |
| (new) Service-account JWT | not tracked | shipped | New scope, see C4a |
No story status changes; the gains are structural rather than story-by-story. The two new capabilities should probably get their own US-XXX entries in a future registry pass.