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
}
}
}
}
}
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.
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"
}
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"
}
}