Authentication¶
Every WebSocket connection is authenticated with a JSON Web Token (JWT) access token during the handshake. After the handshake, message-level authorization is derived from the role carried inside the token.
Reference: connection.md · protocol.md · errors.md
JWT authentication¶
- Token type: access token (JWT claim
type: "access"). - The token must be issued by the Dichit authentication service (the same tokens used by the REST API).
- Refresh tokens are not accepted on the WebSocket. Use the REST refresh endpoint to obtain a new access token, then reconnect.
- The token is verified with the platform signing key; signature, expiry, issuer, and audience are checked.
Token claims used by the socket¶
| Claim | Type | Description |
|---|---|---|
userId | string | The authenticated user id (SUBSCRIBER id or company/admin user id). |
role | SUBSCRIBER \| COMPANY \| SUPERADMIN | The connection's role. |
type | string | Must be "access". |
exp | number | Expiry (epoch seconds). Connections cannot continue past expiry without reconnecting. |
Authentication flow¶
Authentication happens in the upgrade handshake, before the socket is fully open:
- The client opens a WebSocket to
/ws, supplying a valid access token either asAuthorization: Bearer <token>or as theSec-WebSocket-Protocol: websocket.v1, bearer.<token>subprotocol. - The server parses and verifies the token.
- On success, the server builds a typed
SocketUser(id, role, company id, permissions) and opens the connection. - On failure, the server closes the connection with close code
1008and reasonAuthentication failed. No message is processed.
sequenceDiagram
participant C as Client
participant S as WebSocket Server
participant J as JWT Service
C->>S: Upgrade request + Bearer token
S->>J: verifyAccessToken(token)
alt valid access token
J-->>S: decoded claims (userId, role)
S-->>C: 101 Switching Protocols (open)
Note over C,S: authenticated socket context
else invalid / expired / refresh token
J-->>S: verification error
S--xC: close 1008 "Authentication failed"
end Authentication success¶
GET /ws HTTP/1.1
Host: api.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: websocket.v1, bearer.eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Protocol: websocket.v1
The connection is then ready. There is no auth.login message in the current build; authentication is completed by the handshake. See events/auth.md.
Authentication failure¶
When the token is missing, malformed, expired, or not an access token, the server closes the socket immediately:
Example of a failure (browser-visible as an error event):
Token expiration¶
- Access tokens expire. The socket does not silently refresh them.
- Once the token expires, the platform will terminate the connection (or the client's next operation will be treated as unauthenticated).
- Clients must refresh before expiry and reconnect. Obtain a fresh access token via the REST refresh endpoint, then open a new socket; then re-subscribe (rooms and auctions) and resume using
lastSequencewhere applicable.
Recommended timing: refresh when the token's remaining lifetime drops below a safety margin, and proactively reopen the socket during a quiet period.
Re-authentication¶
Re-authentication is connection-level, not message-level:
- If a connection drops, close it and open a new one with a fresh token.
- There is no in-band re-login command in the current build (
auth.loginis a planned in-band event; see events/auth.md). - After reconnecting, rejoin rooms and re-subscribe to auctions; use
auction.snapshotandlastSequencereplay to restore state.
Authorization model¶
Authorization is message-level. Each socket route declares the permissions it requires. The server derives the connection's permission set from its role at handshake time and checks every message against the route's required permissions.
- If the connection lacks any required permission, the server replies with
FORBIDDEN(code: "FORBIDDEN"). - Unknown event types reply with
BAD_REQUEST. - Permission checks run after validation and rate limiting, before the handler.
The pipeline is documented in protocol.md and diagrammed in diagrams.md.
Socket identity¶
interface SocketUser {
readonly id: string; // user id
readonly role: Role; // SUBSCRIBER | COMPANY | SUPERADMIN
readonly companyId?: string; // set for COMPANY (currently = userId)
readonly permissions: readonly string[];
}
For COMPANY connections, companyId is currently derived from userId; room access rules use it to gate the company:<companyId> room.
Roles¶
The platform exposes four client personas over one endpoint. They map to the three token roles as follows:
| Persona | Token role | Description |
|---|---|---|
| Subscriber | SUBSCRIBER | Auction participant who bids and observes live auctions. |
| Company / Auctioneer | COMPANY | Auctioneer staff who run, moderate, and close auctions, and place offline bids. |
| Admin | SUPERADMIN | Platform administrator with staff powers plus admins room access and global broadcasts. |
Each persona connects to the same wss://api.example.com/ws endpoint; only the derived permissions differ.
Permissions¶
Permissions are role-derived and fixed at handshake time:
| Permission | SUBSCRIBER | COMPANY | SUPERADMIN | Effect |
|---|---|---|---|---|
auction:subscribe | ✅ | ✅ | ✅ | Join live auction rooms (auction.subscribe). |
auction:bid:place | ✅ | ❌ | ❌ | Place live bids (auction.bid.place). |
auction:staff | ❌ | ✅ | ✅ | Run staff commands (auction.status.update, auction.pause, auction.resume, auction.end, bid.mark, winner.declare, winner.record_lot). |
broadcast:receive | ❌ | ✅ | ✅ | Receive platform/broadcast events. |
room:join | ✅ | ✅ | ✅ | Join rooms via room.join. |
room:leave | ✅ | ✅ | ✅ | Leave rooms via room.leave. |
Notes:
- A missing permission produces
FORBIDDEN, neverUNAUTHORIZED. The connection is authenticated; it simply lacks authority. - The permission model is additive and may grow over time. New permissions are introduced in a backward-compatible way (see versioning.md).
Permission check example¶
A SUBSCRIBER sending auction.pause:
{
"type": "error",
"requestId": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"code": "FORBIDDEN",
"message": "You are not authorized to perform this WebSocket action."
}
Request/response examples¶
Handshake (header transport):
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJzdWJfMDFIUTVCWFdZUCIsInJvbGUiOiJTVUJTQ1JJQkVSIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDE2NjQwMH0.signature
Handshake (browser-compatible subprotocol transport):
Related documents¶
- events/auth.md — planned in-band
auth.login/auth.logout. - events/system.md — unauthenticated
system.ping. - rooms.md — room-level access rules that build on these permissions.
- diagrams.md — authorization flow diagram.