Skip to main content

Channel JWT authorization

In the chapter about channel permissions we mentioned that to subscribe on a channel client can provide subscription token. This chapter has more information about the subscription token mechanism in Centrifugo.

Subscription token is also JWT. The concept is very similar to the connection token, but with specific custom claims.

Valid subscription token passed to Centrifugo in a subscribe request will tell Centrifugo that subscription must be accepted.

See more info about working with subscription tokens on the client side in client SDK spec.

tip

Connection token and subscription token are both JWT and both can be generated with any JWT library.

tip

Even when authorizing a subscription to a channel with a subscription JWT you should still set a proper connection JWT for a client as it provides user authentication details to Centrifugo.

tip

Just like connection JWT, using subscription JWT with a reasonable expiration time may help you maintain a good level of security in channels and still survive a massive reconnect scenario โ€“ when many clients resubscribe all at once.

Supported JWT algorithms for private subscription tokens match algorithms to create connection JWT. The same HMAC secret key, RSA, and ECDSA public keys set for authentication tokens are re-used to check subscription JWT.

Subscription JWT claimsโ€‹

For subscription JWT Centrifugo uses some standard claims defined in rfc7519, also some custom Centrifugo-specific.

subโ€‹

This is a standard JWT claim which must contain an ID of the current application user (as string).

The value must match a user in connection JWT โ€“ since it's the same real-time connection. The missing claim will mean that token issued for anonymous user (i.e. with empty user ID).

channelโ€‹

Required. Channel that client tries to subscribe to with this token (string).

infoโ€‹

Optional. Additional channel-specific information about connection (valid JSON). This information will be included:

  • in online presence data
  • join/leave events
  • and into client-side channel publications

b64infoโ€‹

Optional. Additional information for connection inside this channel in base64 format (string). Will be decoded by Centrifugo to raw bytes.

expโ€‹

Optional. This is a standard JWT claim that allows setting private channel subscription token expiration time (a UNIX timestamp in the future, in seconds, as integer) and configures subscription expiration time.

At the moment if the subscription expires client connection will be closed and the client will try to reconnect. In most cases, you don't need this and should prefer using the expiration of the connection JWT to deactivate the connection (see authentication). But if you need more granular per-channel control this may fit your needs.

Once exp is set in token every subscription token must be periodically refreshed. This refresh workflow happens on the client side. Refer to the specific client documentation to see how to refresh subscriptions.

expire_atโ€‹

Optional. By default, Centrifugo looks on exp claim to both check token expiration and configure subscription expiration time. In most cases this is fine, but there could be situations where you want to decouple subscription token expiration check with subscription expiration time. As soon as the expire_at claim is provided (set) in subscription JWT Centrifugo relies on it for setting subscription expiration time (JWT expiration still checked over exp though).

expire_at is a UNIX timestamp seconds when the subscription should expire.

  • Set it to the future time for expiring subscription at some point
  • Set it to 0 to disable subscription expiration (but still check token exp claim). This allows implementing a one-time subscription token.

audโ€‹

By default, Centrifugo does not check JWT audience (rfc7519 aud claim). But if you set client.token.audience option as described in client authentication then audience for subscription JWT will also be checked.

issโ€‹

By default, Centrifugo does not check JWT issuer (rfc7519 iss claim). But if you set client.token.issuer option as described in client authentication then issuer for subscription JWT will also be checked.

iatโ€‹

This is the UNIX time when the token was issued (in seconds). See definition in RFC. This claim is optional but can be useful together with Centrifugo PRO token revocation features.

jtiโ€‹

This is a token unique ID. See definition in RFC. This claim is optional but can be useful together with Centrifugo PRO token revocation features.

overrideโ€‹

One more claim is override. This is an object which allows overriding channel options for the particular channel subscriber which comes with subscription token.

FieldTypeRequiredDescription
presenceBoolValuenooverride presence channel option
join_leaveBoolValuenooverride join_leave channel option
force_push_join_leaveBoolValuenooverride force_push_join_leave channel option
force_recoveryBoolValuenooverride force_recovery channel option
force_positioningBoolValuenooverride force_positioning channel option

BoolValue is an object like this:

{
"value": true/false
}

So for example, you want to turn off emitting a presence information for a particular subscriber in a channel:

{
...
"override": {
"presence": {
"value": false
}
}
}

Try it: subscription JWT explorerโ€‹

Build a subscription token below and see whether the subscription is authorized (the claim-level checks Centrifugo runs) and what it becomes โ€” the channel, user, expiration, info, and any channel-option overrides โ€” along with the JWT claims to sign and the matching config.

Subscription JWT explorer

ACCEPTED
Subscription
channel โ€” required
sub โ€” user ID (must match the authenticated user ID)
Expiration
exp โ€” token / subscription expiration
expire_at โ€” separate subscription expiration
info & override
info โ€” channel-specific, shown in presence / join-leave (JSON)
override presence
override join_leave
override force_push_join_leave
override force_recovery
override force_positioning
Audience & issuer โ€” recommended
Token aud / server client.token.audience
Token iss / server client.token.issuer
Subscription tokens reuse client.token.audience / client.token.issuer โ€” setting them is good practice.
Verification (assuming a valid signature)
  • ยทAudience โ€” client.token.audience not set โ€” audience is not verified.
  • ยทIssuer โ€” client.token.issuer not set โ€” issuer is not verified.
  • โœ“Expiration โ€” exp is in the future.
  • โœ“Channel claim โ€” Authorizes subscription to "chat:room42".
  • ยทUser match โ€” The tokenโ€™s sub must equal the connectionโ€™s authenticated user ID, or Centrifugo rejects the subscription at subscribe time.
Resulting subscription
channelchat:room42
user42
expireswhen the token exp is reached
overridesnone (namespace defaults)
Models Centrifugo OSS subscription-token claim checks and the resulting subscription. Decoding reads the header and payload only โ€” signature verification and the PRO allow capability claim are not performed here. In build mode exp values are example timestamps.

Example: create subscription JWTโ€‹

Generate a subscription token to test with right below โ€” signed in your browser (the secret never leaves the page) โ€” with the backend code to issue it in several languages and how a client subscribes with it.

Subscription token generator โ€” HS256, in your browser

โ€ฆ
Claims
sub โ€” user ID (leave empty for anonymous)
channel โ€” required
expires in
Signing key
client.token.hmac_secret_key from your config๐Ÿ”’ Signed locally with the Web Crypto API โ€” the secret never leaves your browser (no network request is made).
Token
paste this as the client token
โ€ฆ
config that verifies it
{
  "client": {
    "token": {
      "hmac_secret_key": "my-secret-key"
    }
  }
}
Everything here runs entirely in your browser. The secret you type is used only to sign the token locally (Web Crypto) and is never sent to any server โ€” not to Centrifugo, not to this docs site. You can confirm in your browser's Network tab: nothing is requested. It is still for local development / testing only โ€” don't paste a production secret into any web page. In production:
  • Your backend issues tokens (the code above), never the browser โ€” the HMAC secret must stay on the server.
  • Use a strong, random secret (32+ bytes) โ€” centrifugo genconfig generates one; keep it in an environment variable / secret store, not in code.
  • Prefer RSA/ECDSA (public/private keys) or a JWKS endpoint if tokens are issued by a separate identity provider.
  • Keep a short exp and refresh via getToken, so access can be revoked in minutes.

You can also write it by hand. Here's a subscription token in Python (assuming user ID is 42 and the channel is gossips):

import jwt
import time

claims = {"sub": "42", "channel": "$gossips", "exp": int(time.time()) + 3600}
token = jwt.encode(claims, "secret", algorithm="HS256").decode()
print(token)

Where "secret" is the client.token.hmac_secret_key from Centrifugo configuration (we use HMAC tokens in this example which relies on a shared secret key, for RSA or ECDSA tokens you need to use a private key known only by your backend).

Example: subscribe with JWTโ€‹

To subscribe with JWT it should be passed to Centrifugo from the client side while making subscription request.

Our bidirectional SDKs provide options to set initial subscription token for Subscription objects as well as an option to set the function to load new subscription token (required to handle refresh of expiring tokens). See examples in client SDK spec.

gensubtoken cli commandโ€‹

During development you can quickly generate valid subscription token using Centrifugo gensubtoken cli command.

./centrifugo gensubtoken -u 123722 -s channel

You should see an output like this:

HMAC SHA-256 JWT for user "123722" and channel "channel" with expiration TTL 168h0m0s:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM3MjIiLCJleHAiOjE2NTU0NDg0MzgsImNoYW5uZWwiOiJjaGFubmVsIn0.JyRI3ovNV-abV8VxCmZCD556o2F2mNL1UoU58gNR-uI

But in a real app, subscription JWTs must be generated by your application backend.

Separate subscription token configโ€‹

When client.subscription_token.enabled boolean option is true Centrifugo does not look at general token options at all when verifying subscription tokens and uses config options defined under client.subscription_token. section

Here is an example how to use JWKS for connection tokens, but have HMAC-based verification for subscription tokens:

config.json
{
"client": {
"token": {
"jwks_public_endpoint": "https://example.com/openid-connect/certs"
},
"subscription_token": {
"enabled": true,
"hmac_secret_key": "separate_secret_which_must_be_strong"
}
}
}

All the options which are available for connection token configuration may be re-used in a separate subscription token configuration section.