Skip to content

Require caller authorization for ACS token and room endpoints - #291

Merged
Ankesh N Bhoi (ankeshni) merged 1 commit into
mainfrom
ankeshbhoi/authorize-acs-token-and-room-endpoints
Aug 5, 2026
Merged

Require caller authorization for ACS token and room endpoints#291
Ankesh N Bhoi (ankeshni) merged 1 commit into
mainfrom
ankeshbhoi/authorize-acs-token-and-room-endpoints

Conversation

@ankeshni

Copy link
Copy Markdown
Contributor

Fixes the CWE-639 (authorization bypass through user-controlled key) and CWE-862 (missing authorization) alerts on Project/webpack.config.js — 4 alerts covering 2 root issues.

Problem

The dev server holds the ACS resource connection string, but its routes had no authorization layer — the only middleware ahead of them was bodyParser.json(). This is not dead sample code: npm start binds webpack-dev-server to 0.0.0.0, allowedHosts admits .azurewebsites.net, and the README documents Azure App Service deployment.

Any remote caller could:

  • Mint a VoIP token for an arbitrary communicationUserId via POST /getCommunicationUserToken, impersonating any identity in the resource and consuming the owner's ACS quota.
  • Create rooms and re-role participants via POST /createRoom and PATCH /updateParticipant, using the server as a confused deputy. An attacker needed no existing participant ID, since the same server would mint them one.

Fix

A server-side session keyed by an opaque HttpOnly cookie, so each privileged route can prove the caller owns the identity or room it is acting on.

Route Change
POST /getCommunicationUserToken New identities are still provisioned anonymously (the sample's purpose) and recorded on the session. Re-minting for an existing ID requires the session to own it, else 403.
POST /createRoom Requires a session holding an ACS identity; records the creator. Validates participant IDs are non-empty strings, caps at 50, coerces pstnDialOutEnabled to a strict boolean.
PATCH /updateParticipant Requires the session to be the room's creator or to hold an identity already a Presenter in the room. Role validated against an allowlist.

Supporting changes:

  • generateGuid() used Math.random() for OneSignal registration tokens, which are exchangeable for ACS access tokens via /getCommunicationUserTokenForOneSignalRegistrationToken — guessing one bypassed the new checks entirely. Now crypto.randomUUID().
  • Both room handlers ended with throw e, leaking stack traces through default error handling and causing unhandled rejections. Now a JSON 500.
  • Cookie is SameSite=Strict (these are cookie-authorized state-changing routes, so CSRF would otherwise be the next bypass) and Secure behind HTTPS. Sessions carry an 8h TTL and a 1000-entry cap to bound memory.
  • Utils.js: surfaces the server's message on failure instead of Request failed with status code 403, and createRoom no longer throws a TypeError on error.response.data when the response is absent.

Behavior change worth reviewing

Supplying a communicationUserId without a token now only re-mints within the session that created that identity. Cross-session reuse was precisely the impersonation vector, and the sample has no authentication to prove ownership otherwise. The "bring your own token + user ID" login path is unaffected, and the session cookie persists across page reloads so a normal dev session is unchanged.

Verification

The real handlers were exercised against stubbed ACS clients — 17 checks, all passing:

  • Legitimate flows: anonymous provisioning, owner re-mint, room creation, creator update, existing-Presenter update.
  • Attack flows: anonymous mint for a victim ID, cross-session mint, forged session cookie, anonymous room create, cross-session room update, arbitrary role string, non-string participant IDs, participant-count cap, unknown room (returns 403, not a stack trace).

The dev server holds the ACS resource connection string, but its routes had
no authorization layer, and `npm start` binds 0.0.0.0 with `.azurewebsites.net`
in allowedHosts. Any remote caller could mint a VoIP token for an arbitrary
communicationUserId, create rooms, or re-role participants in a known room.

Add a server-side session keyed by an opaque HttpOnly cookie and use it to
authorize the privileged routes:

- POST /getCommunicationUserToken still provisions new identities anonymously,
  but only re-mints a token for an identity the session already owns.
- POST /createRoom requires a session holding an ACS identity, records the
  creator, validates participant IDs, caps participant count, and coerces
  pstnDialOutEnabled to a strict boolean.
- PATCH /updateParticipant requires the session to be the room creator or to
  hold an identity that is already a Presenter in the room, and validates the
  requested role against an allowlist.

The cookie is SameSite=Strict so these cookie-authorized, state-changing routes
are not CSRF-able, and Secure when served over HTTPS.

Also replace the Math.random() GUID used for OneSignal registration tokens with
crypto.randomUUID(), since those tokens can be exchanged for ACS access tokens,
and return JSON 500s from the room handlers instead of rethrowing, which
surfaced stack traces through default error handling.

Addresses CWE-639 and CWE-862 alerts on Project/webpack.config.js.
@ankeshni
Ankesh N Bhoi (ankeshni) merged commit b3d7553 into main Aug 5, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants