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.
Connection token and subscription token are both JWT and both can be generated with any JWT library.
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.
Just like connection JWT using subscription JWT with a reasonable expiration time may help you have a good level of security in channels and still survive massive reconnect scenario – when many clients resubscribe alltogether.
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 tokenexp
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 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 token_issuer
option as described in client authentication then issuer for subscription JWT will also be checked.
iat
This is a UNIX time when token was issued (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.
Field | Type | Optional | Description |
---|---|---|---|
presence | BoolValue | yes | override presence channel option |
join_leave | BoolValue | yes | override join_leave channel option |
force_push_join_leave | BoolValue | yes | override force_push_join_leave channel option |
force_recovery | BoolValue | yes | override force_recovery channel option |
force_positioning | BoolValue | yes | override 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
}
}
}
Example: create subscription JWT
So to generate a subscription token you can use something like this in Python (assuming user ID is 42
and the channel is gossips
):
- Python
- NodeJS
import jwt
import time
claims = {"sub": "42", "channel": "$gossips", "exp": int(time.time()) + 3600}
token = jwt.encode(claims, "secret", algorithm="HS256").decode()
print(token)
const jose = require('jose')
(async function main() {
const secret = new TextEncoder().encode('secret')
const alg = 'HS256'
const token = await new jose.SignJWT({ sub: '42', channel: '$gossips' })
.setProtectedHeader({ alg })
.setExpirationTime('1h')
.sign(secret)
console.log(token);
})();
Where "secret"
is the 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 real app subscription JWT must be generated by your application backend.
Separate subscription token config
When separate_subscription_token_config
boolean option is true
Centrifugo does not look at general token options at all when verifying subscription tokens and uses config options starting from subscription_token_
prefix instead.
Here is an example how to use JWKS for connection tokens, but have HMAC-based verification for subscription tokens:
{
"token_jwks_public_endpoint": "https://example.com/openid-connect/certs",
"separate_subscription_token_config": true,
"subscription_token_hmac_secret_key": "separate_secret_which_must_be_strong"
}
All the options which are available for connection token configuration may be re-used for a separate subscription token configuration – just prefix them with subscription_token_
instead of token_
.