Skip to main content

Claims ✉️

When issuing a JWT, you're allowing the recipient of the token to connect to the WebSocket, access channels and perform operations on the WebSocket, or both. By design, Hotsock has minimal global configuration and allows as much as possible to be specified per-token using signed JWT claims.

At minimum, a token must have an exp claim specifying when the token expires and a scope claim indicating where the the token can be used. If your installation requires a particular aud audience and/or iss issuer claim, these must be specified as well. The following minimal token claims allow connecting to the WebSocket (assuming 1686011549 is a Unix timestamp in the future).

{
"exp": 1686011549,
"scope": "connect"
}

aud - Audience

String or Array[String] (optional, recommended) - The audience claim is optionally required by TokenAudienceParameter when installing or updating the Hotsock stack. If enabled, every issued token must include it with the value set in the installation settings. Its purpose is to ensure that tokens issued by the same signer are not used across multiple services unintentionally.

If you sign Hotsock tokens with a single key across multiple installations for scaling, tenancy, locality, or data residency requirements, requiring a unique aud claim per installation ensures that the user of the key cannot use the key on another tenant's Hotsock installation.

{
"aud": "hotsock-us-east-1"
}

Per the JWT spec, aud can also be an array of audiences, where the installation's TokenAudienceParameter must match any one of the supplied audiences.

{
"aud": ["hotsock-us-east-1", "hotsock-eu-west-2"]
}

channels

Object (optional) - The channels claim specifies the channel permissions that are granted to this connection. Each object key is the name of a channel and can include asterisks (*) anywhere in the string to denote wildcards. Each object value is another object with the settings for that channel or channel wildcard.

The following grants subscribe access to any channel with the prefix account.123. and the channel named user.456.

{
"channels": {
"account.123.*": {
"subscribe": true
},
"user.456": {
"subscribe": true
}
}
}

For wildcards that overlap, any explicit false is used as the value and cannot be overridden, regardless of the order that they are specified. In the following example, the connection can subscribe to chat.* (chat.123, chat.everyone, etc), but not chat.admin.

{
"channels": {
"chat.admin": {
"subscribe": false
},
"chat.*": {
"subscribe": true
}
}
}

Each object inside the channels object accepts messages and subscribe attributes.

historyStart

NumericDate (optional) - Allows the connection to make HTTP API requests to load past messages stored on the channel prior to the time when the current connection's channel subscription was created, where the specified time is the oldest retrievable message. If supplied, the history start time must be expressed as a Unix timestamp - the number of seconds since the Unix epoch. By default, stored message retreval is limited to messages that were published during the lifetime of the active channel subscription.

The following allows retieving stored messages going back to 2024-10-11 00:00:00 UTC, which is 1728604800 as a Unix timestamp.

{
"channels": {
"mychannel": {
"historyStart": 1728604800,
"subscribe": true
}
}
}

If you wanted to allow access to the entire message history of a channel without specifying a particular time, setting historyStart to 0 will do the trick.

messages

Object (optional) - Manages the permissions and directives for client-initiated messages that are published directly to the WebSocket on the channel(s) for the specified events. Each object key is the name of an event and can include asterisks (*) anywhere in the string to denote wildcards. Each object value is another object with the settings for that event or event wildcard.

echo

Boolean (optional) - If publish is permitted for this channel and event, the echo attribute specifies whether or not this connection that is sending these messages will receive a copy of the message on the channel. Default is false. Has no effect if publish resolves to false.

In the following example, a copy of all chat client event messages will be sent to all subscribers, including the sender. is-typing client event messages will be sent to all other subscribers, but the sender will not receive a copy.

{
"channels": {
"mychannel": {
"subscribe": true,
"messages": {
"is-typing": {
"publish": true,
"echo": false
},
"chat": {
"publish": true,
"echo": true
}
}
}
}
}

emitPubSubEvent

Boolean (optional) - If client-initiated messages are permitted for this channel and event, the emitPubSubEvent attribute specifies whether or not these events will trigger a backend pub/sub event to SNS/EventBridge. Default is false. Has no effect if publish resolves to false. Also has no effect if both SNS and EventBridge events are disabled globally.

In the following example, chat client event messages will trigger server-side pub/sub events. is-typing client event messages will not.

{
"channels": {
"mychannel": {
"subscribe": true,
"messages": {
"is-typing": {
"publish": true
},
"chat": {
"echo": true,
"emitPubSubEvent": true
}
}
}
}
}

publish

Boolean (optional) - The following allows this connection to subscribe to the channel "mychannel" and publish client-initiated messages with the "is-typing" event name.

{
"channels": {
"mychannel": {
"subscribe": true,
"messages": {
"is-typing": {
"publish": true
}
}
}
}
}

Wildcards are supported in event names using asterisks (*). The following allows the connection to send any event on "mychannel".

{
"channels": {
"mychannel": {
"subscribe": true,
"messages": {
"*": {
"publish": true
}
}
}
}
}
warning

Be careful with wildcard permissions for client initiated events!

In the following example, since chat.* includes chat.admin, this connection would be able to publish any event to the chat.admin channel, even though they're not able to subscribe to its messages. In this case it would probably be best to name the chat.admin channel differently to ensure permissions to it are never expanded unintentionally.

{
"channels": {
"chat.admin": {
"subscribe": false
},
"chat.*": {
"subscribe": true,
"messages": {
"*": {
"publish": true
}
}
}
}
}

store

Integer (optional) - If client-initiated message publishing are permitted for this channel, the store attribute specifies the number of seconds that written messages from this connection are retained in the database after being sent. 0 is the default if unspecified, which does not store the message. -1 keeps messages forever. The highest number you can specify here is 3155695200, roughly 100 years.

The following allows sending ephemeral "is-typing" events and saved "chat" events to "mychannel", storing each "chat" message for about 1 year (365 days * 24 hours * 60 minutes * 60 seconds) = 31,536,000 seconds.

{
"channels": {
"mychannel": {
"messages": {
"is-typing": {
"publish": true
},
"chat": {
"publish": true,
"store": 31536000
}
}
}
}
}

subscribe

Boolean (optional) - Whether or not to allow the connection to subscribe to the channel(s) specified by the object key. Default is false.

The following sets subscribe to true on "mychannel".

{
"channels": {
"mychannel": {
"subscribe": true
}
}
}

You can also explicitly set subscribe to false, preventing subscriptions to this channel. An explicit false for a channel will always cause a deny, regardless of other permissions (such as wildcards) that may set this value to true.

{
"channels": {
"mychannel": {
"subscribe": false
}
}
}

connectionId

String (optional) - If granting additional permissions to an existing connection in a subscribe token, either connectionId or uid is required. If connectionId is specified, the token cannot be used to initiate a new WebSocket connection and can only be used on the existing connection matching the specified connection ID, even if the token has an connect scope.

{
"connectionId": "GHrCdeIEoAMCKmQ="
}

exp - Expiration

NumericDate (required) - The token expiration must be expressed as a Unix timestamp - the number of seconds since the Unix epoch. It's recommended that this value be set as low as possible, often just seconds in the future. The token must be used before this expiration, but once used it grants up to 2 hours of connection time and use of the WebSocket.

{
"exp": 1686011549
}

iat - Issued At

NumericDate (optional) - The issued at claim identifies the time when the JWT was issued, expressed as a Unix timestamp. Hotsock does not rely on this claim for authorization, but it's common for issuers to provide it by default which is why it's mentioned here.

{
"iat": 1686011549
}

iss - Issuer

String (optional) - The issuer claim is optionally set in TokenIssuerParameter when installing or updating the Hotsock stack. If enabled, every issued token must include it with the value set in the installation settings.

{
"iss": "my-application-issuer"
}

jti - JWT ID

String (optional) - The JWT ID claim provides a unique identifier for the JWT. A UUID is often a good fit.

{
"jti": "0188b190-109f-427a-afcb-98efea6348ba"
}

keepAlive

Boolean (optional) - The keep-alive claim enables server-initiated hotsock.keepAlive messages to be sent to the connection to keep the connection alive as long as possible, preventing server-initiated disconnects when no messages are sent or received for more than 10 minutes. Default is false.

{
"keepAlive": true
}

nbf - Not Before

NumericDate (optional) - The not-before claim specifies the earliest time that a token can be used, expressed as a Unix timestamp. This allows you to issue and distribute tokens prior to them being usable. If combined with singleUse and a failed attempt to use the token before it is valid occurs, it does not count as token use.

{
"nbf": 1686011549
}

scope

String (required) - Space-separated list of scopes for when and where the token can be used. connect and subscribe are currently supported.

To use a token when initiating a WebSocket connection, the connect scope is required. When using a token that provides additional channel subscription permissions not included in the connection token to subscribe to a channel, the subscribe scope is required.

info

You can subscribe to channels authorized by a connect-scoped token once you're connected to the WebSocket. The subscribe scope is only required if you're issuing a token with additional permissions that were not included in the connect token.

{
"scope": "connect"
}
warning

Although Hotsock supports granting multiple scopes, you likely don't want to authorize both connect and subscribe in the same token.

Say you issue a token with {"scope":"connect subscribe","uid":"jim","channels":{...}} with the purpose of allowing Jim to subscribe to a newly-authorized channel after the WebSocket connection has already been established. Dual scopes here would unnecessarily also allow the issued token to open an additional WebSocket connection alongside the existing one.

singleUse

Boolean (optional) - Setting to true ensures that this JWT is only used for a single connect operation (when used with a connect scope) or a single subscribe operation (when used with a subscribe scope). Follow-up attempts to use the same JWT results in denied access. Default is false.

{
"singleUse": true
}

uid - User ID

String (optional) - The user ID claim identifies the connected user in any channels where they subscribe. It is available to all other subscribers when joining presence channels and included in all published messages that are initated by this connection. Must not exceed 128 characters.

The uid claim is required (and must not be empty) if you're using a subscribe scoped token and are not specifying a connectionId. This grants channel subscribe access to any connection with the matching uid.

The uid claim is required (and must not be empty) when joining a presence channel.

{
"uid": "12345"
}

umd - User Metadata

JSON (optional) - The user metadata claim may contain up to 1 KiB of data about this connected user. It is made available to all other members in subscribed presence channels and is included in all published messages that are initiated by this connection. This can be anything that is valid JSON - object, array, string, number, boolean, or null. Binary data must be Base64-encoded to a string.

{
"umd": {
"name": "Dwight",
"title": "Assistant To The Regional Manager"
}
}