5.2 KiB
Socket Broker
A Socket.IO broker with topic-based messaging for ComedyKit. Backend services publish over an HTTP API protected by an API key; browsers and mobile clients authenticate with short-lived, topic-scoped tokens issued by Laravel.
Features
- Socket.IO server for real-time client subscriptions.
- HTTP API for backend publishing.
- API-key authentication for publishers.
- HMAC-SHA256 client tokens for connection and topic authorization.
- Room-based topic delivery (no in-memory subscription maps).
- Protected topic introspection and public health check.
- CORS allowlist, subscription limits, and subscribe rate limiting.
Architecture
Frontend / mobile clients
│ Socket.IO (connection token + topic token)
▼
Socket broker ── rooms: user/{uuid}/notifications
▲ community/user/{uuid}
│ event/{slug}/ticket_statistics
│ POST /publish (API key)
Laravel backend
Configuration
Copy env.example to .env:
| Variable | Default | Description |
|---|---|---|
WS_PORT |
3000 |
Socket.IO / HTTP port |
API_PORT |
WS_PORT |
Optional second HTTP API port |
API_KEY |
default-api-key-change-me |
Publisher API key; never expose to clients |
SOCKET_TOKEN_SECRET |
— | Shared HMAC secret; must match Laravel SOCKET_TOKEN_SECRET |
AUTH_MODE |
required |
off, optional (rollout only), or required |
ALLOWED_ORIGINS |
https://*.comedykit.be,... |
Comma-separated origins; supports *.domain.tld |
MAX_TOPICS_PER_SOCKET |
20 |
Subscription cap per connection |
SUBSCRIBE_RATE_LIMIT |
30 |
Subscribe attempts per 10 seconds per connection |
AUTH_MODE=required refuses to start without SOCKET_TOKEN_SECRET.
Rollout
- Deploy with
AUTH_MODE=optionalso legacy clients still work. - Deploy Laravel with
/notifications/socket-tokenand the new publisher job. - Deploy the updated web/mobile clients.
- Switch to
AUTH_MODE=required.
Client authentication
Laravel issues two token types:
// Connection token
{ "v": 1, "sub": "<user uuid>", "typ": "connect", "iat": 0, "exp": 0, "jti": "..." }
// Topic token
{ "v": 1, "sub": "<user uuid>", "typ": "topic", "topic": "event/slug/ticket_statistics", "iat": 0, "exp": 0, "jti": "..." }
Tokens are encoded as:
base64url(JSON payload) + "." + base64url(HMAC-SHA256(payload, SOCKET_TOKEN_SECRET))
Clients connect with the connection token:
const socket = io("https://bram-sockets.comedykit.be", {
auth: { token: connectionToken },
});
A user may always subscribe to user/{their-own-id}/notifications and community/user/{their-own-id}. Any other topic requires a topic token whose sub matches the connection subject and whose topic matches the subscription exactly.
WebSocket API
Subscribe
socket.emit("subscribe", "user/<uuid>/notifications");
// Or, for a topic-scoped token:
socket.emit("subscribe", {
topic: "event/my-event/ticket_statistics",
token: topicToken,
});
socket.on("subscribed", data => {
console.log(`Subscribed to ${data.topic}`);
});
Unsubscribe
socket.emit("unsubscribe", "user/<uuid>/notifications");
socket.on("unsubscribed", data => {
console.log(`Unsubscribed from ${data.topic}`);
});
Receive messages
socket.on("message", data => {
console.log(data.topic, data.message, data.data, data.timestamp);
});
Errors
socket.on("error", error => {
// INVALID_TOPIC | FORBIDDEN_TOPIC | RATE_LIMITED | TOPIC_LIMIT
console.error(error.code, error.message);
});
HTTP API
Public
GET /health— status, connection count, auth mode. Does not expose topic names.
Protected (API key)
GET /topicsGET /topics/:topicPOST /publishPOST /publish/:topic
API key is accepted as Authorization: Bearer <key>, x-api-key, or api_key query parameter.
Publish
curl -X POST http://localhost:3000/publish \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"topic":"user/<uuid>/notifications","message":"{\"id\":\"...\"}"}'
Response:
200with{ success, message, subscribers, payload }204when the topic has no subscribers400invalid request401invalid or missing API key
Development
npm install
npm test
AUTH_MODE=optional API_KEY=dev-key SOCKET_TOKEN_SECRET=dev-secret npm start
Docker
cp env.example .env
# set API_KEY, SOCKET_TOKEN_SECRET, AUTH_MODE, ALLOWED_ORIGINS
docker compose up -d
The Docker image runs as a non-root user and exposes port 3000. If API_PORT differs from WS_PORT, a second HTTP server is started on that port.
Security notes
API_KEYis publisher-only. Never ship it to clients.SOCKET_TOKEN_SECRETmust be at least 32 random bytes and identical in Laravel and the broker.- Tokens are short-lived. The broker verifies them when a subscription is created; clients re-fetch tokens on reconnect.
- Use HTTPS/WSS in production.
- Rotate secrets by coordinating Laravel and broker deployments; outstanding tokens become invalid.
AUTH_MODE=optionaloroffis for local development or the rollout window only.