FleetbaseFleetbase

Socket Authentication

How the console socket service authenticates with short-lived socket tokens, and what extension code that subscribes to realtime channels needs to know.

Socket Authentication

The socket service connects the console to the SocketCluster server with a short-lived socket token minted from the console session. This page covers what that means for extension code that subscribes to channels.

What the service does

  • Handshake. The service gives the SocketCluster client an in-memory auth engine. Every connect and reconnect asks it for a token: while the user is signed in, the engine calls POST int/v1/socket/token and returns { token, expires_in, expires_at }. A cached token is reused while it has more than 60 seconds left. Subscriptions made before the handshake are sent after it, so they are authenticated too. The token is never stored in localStorage and never added to the socket URL.
  • Anonymous fallback. With no session, or when the server cannot mint tokens (an older server, or socket auth turned off, which answers 404), the client connects anonymously, as it did before socket auth existed.
  • Refresh. The service authenticates the open socket with a new token 60 seconds before the current one expires.
  • Recovery. If the server drops the token (deauthenticate), or removes a channel for a token reason (kickOut, or subscribeFail with an AuthError whose reason is no_token, token_expired, token_changed, deauthenticated or identity_changed), the service gets a fresh token, authenticates, and subscribes to the lost channels again by name. Each channel is retried once per token, and there are at most 3 recoveries a minute.
  • Session lifecycle. Login and an organization switch re-key the socket with a new token. On restore, an anonymous socket is authenticated once the user has loaded. Logout clears the token, stops the refresh timer and disconnects.
  • Client tag. The handshake URL carries ?client=console/<version>. This value is not a secret; the server uses it to label its authorization logs.

What extension authors need to know

Channel names are unchanged. Keep subscribing the way you do now, either with socket.listen(channel, callback) or with socket.instance().subscribe(channel) and a for await loop. You don't need to pass a token or call authenticate yourself.

Subscriptions are authorized on the server. Once a server enforces socket auth, the socket server checks each subscribe against the API. A user can only subscribe to channels for records their organization owns, plus their own user.* and company.* channels. A channel name for another organization's record is refused even if the client knows the id.

A recovered channel keeps delivering to your existing loop. SocketCluster sends a channel's data by name. When the service resubscribes a channel after a token problem, the for await loop you started keeps getting messages, and you don't need to subscribe again.

Handle subscribeFail for channels the user may not see. A refusal for any other reason (the user may not access that record) is not retried, so show it or log it instead of waiting forever:

const channel = this.socket.instance().subscribe(`order.${order.public_id}`);

(async () => {
    for await (const { error } of channel.listener('subscribeFail')) {
        // error.name === 'AuthError'; error.reason is a short snake_case code
        // such as `forbidden` or `unknown_prefix`.
        this.notifications.warning(error.message);
    }
})();

(async () => {
    for await (const output of channel) {
        // handle the event
    }
})();

Close the channel when you are done with it (channel.close(), or socket.closeChannels() for channels opened with listen). Closing also ends the subscribeFail listener loop.

Server-side publishing only. Clients cannot publish, transmit or invoke on the socket once it enforces auth. All realtime events come from the API.

Socket Authentication | Fleetbase