---
title: Realtime Authorization Spec
tags: [security, realtime, socketio, authorization]
created: 2026-05-24
status: advisory
---

# Realtime Authorization Spec

This document defines the target authorization model for Socket.IO events in the
escrow platform. It closes Taskmaster subtask 4.4 alongside
[[Authorization Matrix - REST and Socket.IO]].

## 1. Decision

Socket.IO must use the same trust boundary as REST:

- every socket connection is authenticated during the handshake,
- room membership is derived by the server,
- clients cannot subscribe to rooms by supplying arbitrary user, request, chat,
  seller, or buyer IDs,
- server-to-client emissions are targeted to authorized rooms only,
- sensitive payment, payout, dispute, delivery-code, and chat payloads are never
  sent through global broadcasts.

## 2. Handshake Authentication

Client connects with an access token in `handshake.auth.token`.

Server requirements:

1. verify the JWT signature and standard claims,
2. reject expired, malformed, revoked, or missing tokens,
3. attach `{ userId, roles, sessionId, jti }` to `socket.data`,
4. disconnect immediately when authentication fails,
5. log authentication failures without recording token values.

Refresh tokens are not accepted by Socket.IO. Clients must refresh through REST
and reconnect with a fresh access token.

## 3. Server-Derived Base Rooms

On successful connection the server may join only rooms derivable from the
authenticated principal:

| Room | Eligibility | Source |
|---|---|---|
| `user-{userId}` | authenticated user | JWT subject |
| `seller-{userId}` | authenticated user with seller role | JWT roles or user record |
| `buyer-{userId}` | authenticated user with buyer role | JWT roles or user record |
| `sellers` | authenticated seller | JWT roles or user record |
| `buyers` | authenticated buyer | JWT roles or user record |

Clients must not provide `userId`, `sellerId`, or `buyerId` to join these rooms.

## 4. Resource Rooms

Resource rooms require database authorization before join.

| Room | Eligibility | Authorization Query |
|---|---|---|
| `request-{requestId}` | buyer, selected seller, assigned admin/moderator | purchase request participant check |
| `chat-{chatId}` | chat participant or assigned support/admin user | chat participant check |
| `dispute-{disputeId}` | dispute party, assigned moderator, admin | dispute participant/assignment check |
| `template-checkout-{checkoutId}` | checkout owner or service-controlled UI session | checkout ownership check |

Membership must be rechecked when ownership or state changes. If a request,
chat, or dispute loses a participant, the server must remove that user's sockets
from the associated room.

## 5. Client Event Policy

Allowed client-originated events:

| Event | Required Authorization | Notes |
|---|---|---|
| `join-request-room` | participant check | May remain only as a request for server validation. |
| `leave-request-room` | current membership | User may leave an allowed room. |
| `join-chat-room` | participant check | May remain only as a request for server validation. |
| `leave-chat-room` | current membership | User may leave an allowed room. |
| `typing-start` / `typing-stop` | current `chat-{chatId}` membership | `userId` in payload is ignored; server derives sender. |

Removed or deprecated client-originated events:

| Event | Replacement |
|---|---|
| `join-user-room` | server auto-join on handshake |
| `join-seller-room` / `join-buyer-room` | server auto-join from authenticated role |
| `user-online` | server emits presence after authenticated connection |

## 6. Emission Policy

Server emissions must target the narrowest authorized room:

| Data Class | Allowed Target |
|---|---|
| user notifications | `user-{recipientId}` |
| buyer/seller offer updates | relevant `user-*`, `buyer-*`, `seller-*`, or `request-*` room |
| payment status | buyer and seller user rooms, request room if both parties may see it |
| payout status | seller user room and admin operations room only |
| delivery code | seller user room only |
| chat messages | `chat-{chatId}` |
| dispute events | `dispute-{disputeId}` and assigned admin/moderator room |

Global payment and payout events are prohibited because they expose financial
metadata to unrelated users.

## 7. Payload Rules

- Never trust `userId`, `role`, `sellerId`, or `buyerId` from socket payloads.
- Derive sender identity from `socket.data.userId`.
- Do not emit delivery verification codes to buyer-visible rooms.
- Redact wallet addresses, tx hashes, and provider references unless the target
  user is a party to the transaction or an authorized operator.
- Keep payload schemas consistent with REST read permissions.

## 8. Rate Limiting and Audit

Socket event rate limits:

| Event Class | Limit |
|---|---|
| room join attempts | 30 per 15 minutes per user |
| typing events | 120 per minute per socket |
| chat message events | same policy as REST chat message creation |
| failed authorization checks | 10 per 15 minutes per user, then disconnect |

Audit log required for:

- failed room authorization checks,
- admin/moderator joins to dispute or request rooms,
- attempts to join user/seller/buyer rooms for another principal,
- global payment or payout emission rejection.

## 9. Tests

Minimum verification before launch:

1. invalid or missing JWT cannot connect,
2. user cannot join another user's `user-*`, `seller-*`, or `buyer-*` room,
3. user cannot join a request/chat/dispute room without participant status,
4. removed participant is evicted from the resource room,
5. payment and payout events are not emitted globally,
6. delivery code is emitted only to the seller,
7. socket event rate limits disconnect abusive clients,
8. audit events are written for denied room joins.

## Related

- [[Authorization Matrix - REST and Socket.IO]]
- [[Threat Model - Amanat Escrow Platform]]
- [[Session and Authentication Architecture Decision]]
- [[Backend Stack Security and Refactor Assessment - 2026-05-24]]
