Skip to content

feat: allow replacing the auth token on a live JanusClient - #202

Open
Creiger wants to merge 1 commit into
januscaler:masterfrom
Creiger:master
Open

Creiger wants to merge 1 commit into
januscaler:masterfrom
Creiger:master

Conversation

@Creiger

@Creiger Creiger commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

What this changes

Adds a getter and setter for the auth token on JanusClient:

String? get token => _token;

set token(String? value) => _token = value;

That's the whole change. _tokenMap is already read per request rather than cached, so assigning a new token takes effect on the next request.

Why

Janus validates the token on every request, keepalive included — not only on create and attach. With signed tokens (token_auth_secret), that means an expiring token silently kills a live session:

  1. keepalives start returning 403 Unauthorized request (wrong or missing secret/token)
  2. Janus stops seeing the session as alive
  3. after session_timeout (60 s by default) it destroys the session
  4. ICE goes down and the call drops

Because the token can only be set in the constructor, its TTL becomes a hard ceiling on session lifetime. With a 5-minute token, every session dies 6 minutes after it was created. That is how we found this: voice dropped at a fixed interval after connecting, on every client, staggered by connect time.

Evidence

Tested against Janus 1.4.1 with token_auth = true and token_auth_secret set, over a raw janus-protocol WebSocket.

Keepalive is rejected once the token expires (TTL 15 s, keepalive every 3 s):

t=  0s  session created
t= 15s  keepalive: ack            <- last one accepted
t= 18s  keepalive: error 403 Unauthorized request (wrong or missing secret/token)
t= 77s  Janus sends {"janus":"timeout"}   <- 15s + session_timeout (60s)

Control: with a 300 s token, 45 s of keepalives every 3 s → 14 ack, 0 failures. Keepalive itself is fine; the only variable is expiry.

Janus accepts a new token on an already-running session. Same session id, one second apart:

t= 12s  keepalive with the EXPIRED token  ->  error 403 Unauthorized
t= 12s  keepalive with a FRESH token      ->  ack

Janus has no notion of "refreshing" a token because it validates statelessly per request — sending a different string is all that is needed, and the session survives with no reconnect, renegotiation or audio gap.

Impact

None for existing callers. The behaviour is identical unless the setter is called.

We are running this in production in a push-to-talk application: both a Flutter iOS app and a Flutter Web admin fetch a fresh short-lived token from their backend every 4 minutes under a 5-minute TTL and assign it. Sessions now survive indefinitely, and voice no longer drops on a timer.

Note

The same argument applies to _apiSecret, which is stored the same way. We left it alone because it does not normally expire — happy to add it if you'd prefer symmetry.

Janus validates the token on every request, `keepalive` included. Once a
signed token expires, keepalives start failing with 403 Unauthorized, Janus
stops seeing the session as alive and destroys it after `session_timeout`
(60 s by default). Without a way to replace the token, its TTL is a hard
ceiling on session lifetime — our voice calls dropped at a fixed interval
after connecting.

`_tokenMap` is already read per request rather than cached, so exposing the
field is enough: assigning a fresh token takes effect on the next request and
the session survives, with no reconnect, renegotiation or audio gap.

Verified against Janus 1.4.1 (`token_auth` + `token_auth_secret`): the same
session that answers 403 with an expired token answers `ack` a second later
once a fresh token is sent.

Behaviour is unchanged for callers that never touch the setter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant