soli-sfu

Tokens

A token is a signed, expiring pass for one user in one room. The format matches bonfire's GatherWs tokens, so a Soli app mints them with builtins it already has, and the SFU never calls back into the app.

Format

sfu1.<base64(user_id)>:<base64(room)>:<unix_exp>.<hmac>

hmac = HMAC-SHA256("sfu1." + payload, secret), lowercase hex

Minting from Soli

static def mint_sfu_token(user_id, room)
  secret = getenv("SOLI_SFU_SECRET") ?? getenv("SOLI_WEBHOOK_SECRET") ?? ""
  return "" if secret.blank?

  exp     = DateTime.utc().to_unix() + 3600
  payload = Base64.encode(user_id) + ":" + Base64.encode(room) + ":" + str(exp)
  signed  = "sfu1." + payload
  signed + "." + Crypto.hmac(signed, secret)
end

Crypto.hmac is HMAC-SHA256 with lowercase hex. The Rust side pins that exact construction in a unit test, so drift on either side fails CI. Keep lifetimes short: an hour is plenty, since a token is only needed to join and to change groups.

Where the secret comes from

OrderSource
1SOLI_SFU_SECRET environment variable. A dedicated secret is best.
2secret in the TOML config.
3SOLI_WEBHOOK_SECRET, to share bonfire's existing secret.

The server refuses to start with no secret unless dev tokens are enabled.

Minting from the command line

SOLI_SFU_SECRET=... soli-sfu mint-token user-42 spatial:acme 3600
# sfu1.dXNlci00Mg==:c3BhdGlhbDphY21l:1781240000.9f3a…

Dev tokens

With allow_unauthenticated = true, as in dev.toml, the SFU also accepts unsigned dev.<user_id>.<room> tokens. The test client sends one when its token field is empty.

Never turn this on in production. A dev token has no signature and no expiry, so anyone who can reach the control API can join any room. The server logs a warning at startup when it is on.

What a token does not cover

  • It does not encrypt media: DTLS-SRTP does that for every session.
  • It does not decide who hears whom inside a room. Whoever holds the token sets peers; read the security model before relying on it for privacy.