Jitsi-meet-tokens chronicles on Debian Buster

right

yes

nope

JWT with PHP

mod_token_affiliation compatible token

packages

apt-get install php-cli composer
composer require firebase/php-jwt

Simple code

jwt.php

<?php
require_once 'vendor/autoload.php';
use \Firebase\JWT\JWT;

$LINK = "https://meet.mydomain.com";
$ROOM = "myroom";

$key = "mysecret";
$payload = array(
    "aud" => "myapp",
    "iss" => "myapp",
    "sub" => "meet.mydomain.com",
    "exp" => time() + (60*60),
    "room" => "$ROOM",
    "context" => array(
        "user" => array(
            "name" => "username",
            "email" => "username@mydomain.com",
            "avatar" => "https://gravatar.com/avatar/abc123.png",
            "affiliation" => "owner",
        ),
        "features" => array(
            "recording" => true,
            "livestreaming" => true,
            "screen-sharing" => true,
        )
    )
);

$jwt = JWT::encode($payload, $key);
echo $LINK . '/' . $ROOM . '?jwt=' . $jwt;
echo "\n";
?>

running

php jwt.php

I finally got token-based authentication working mostly. But I observe some strange effects. My PHP code to create a token looks as follows. The PHP variable $participation is the result of a SQL query. I hope you get the intention based on the variable names:

$jwtPayload = array(
  "aud" => appName,
  "iss" => appName,
  "sub" => domain,
  "nbf" => $participation['start_time'] - (5*60),
  "iat" => time(),
  "exp" => $participation['end_time'] + (60*60),
  "room" => $participation['conference_name'],
  "moderator" => $participation['is_moderator'],
  "context" => array(
    "user" => array(
      "name" => $participation['display_name'],
      "email" => $participation['email'],
      "affiliation" => $participation['is_moderator'] ? "owner" : "member",
    ),
    "features" => array(
      "recording" => $participation['may_record'],
      "livestreaming" => $participation['may_live_stream'],
      "screen-sharing" => $participation['may_share_screen'],
    )
  )
);

The effects I observe are as follows:

  1. A user which only has affiliation member cannot create a room. Such a user must wait until the room has been created by an owner and can join afterwards. → Good, result as expected
  2. However, if the last owner hast left the room, members may stay in the room as long as they want. They are not kicked out. → Bad, result is unexpected
  3. Granting rights to the features recording and livestreaming does not work as expected. The ability to record/livestream seems to be a conference wide setting depending on the values of the last joining user; i.e. if a user joins who must not record/livestream the feature get disabled for everybody, contrary if a user joins who is allowed to record/livestream the feature gets enabled for everybody → Bad, result is unexpected
  4. Everybody is allowed to share his/her screen irrespective of the boolean value of screen-sharing. → Bad, result is unexpected
  5. Everybody seems to have partial rights of a moderator, irrespective of the the boolean value of moderator. For example, everybody can mute everyone else. → Bad, result is unexpected
  6. Contrary, some rights of the moderator are missing for everyone, irrespective of the the boolean value of moderator. For example, the feature “kick out …” does not work at all. → Bad, result is unexpected
  7. The value of user.name is used as a display name for the video stripe at the right, the tile view and for status messages, i.e. Jon Doe has entered/left the room. → Good, result as expected

Update on item 2:

If a user who has been a participant leaves the room, the module Token Owner Party also broadcasts the message “The owner is gone”. Hence, it seems that a user affiliation somehow magically changes from “member” to “owner” at some point of time. The test user has definitely been a “member” in the first place, because the test user was not allowed to create the room.

I have none of these “bad results” in my system, probably there are some missing steps while installing. You can try jitsi-school-installer

I am very sure I did not miss any step. And I am not destroying my installation I got so far by running a script which simply floors my current setup. Instead, I would like to understand what is going on.

Do you have org.jitsi.jicofo.DISABLE_AUTO_OWNER=true in /etc/jitsi/jicofo/sip-communicator.properties

Yes, I do.

/etc/prosody/conf.d/jitsi.my-domain.tld.cfg.lua

plugin_paths = { "/usr/share/jitsi-meet/prosody-plugins/" }
...
VirtualHost "jitsi.my-domain.tld"
    authentication = "token"
    app_id = "jitsi"
    app_secret = "<my_secret>"
    disable_room_name_constraints = true
    allow_empty_token = false
    ...
    modules_enabled = {
        "bosh";
        "pubsub";
        "ping"; -- Enable mod_ping
        "speakerstats";
        "turncredentials";
        "conference_duration";
        "muc_lobby_rooms";
        "presence_identity";
    }
    c2s_require_encryption = false
    lobby_muc = "lobby.jitsi.my-domain.tld"
    main_muc = "conference.jitsi.my-domain.tld"

Component "conference.jitsi.my-domain.tld" "muc"
    storage = "memory"
    modules_enabled = {
        "muc_meeting_id";
        "muc_domain_mapper";
        "token_verification";
        "token_affiliation";
        "token_owner_party";
    }
   admins = { "focus@auth.jitsi.my-domain.tld" }
   party_check_timeout = 60
   muc_room_locking = false
   muc_room_default_public_jids = true

...

/etc/jitsi/jicofo/sip-communicator.properties

org.jitsi.jicofo.BRIDGE_MUC=JvbBrewery@internal.auth.jitsi.my-domain.tld
org.jitsi.jicofo.auth.URL=EXT_JWT:jitsi.my-domain.tld
org.jitsi.jicofo.DISABLE_AUTO_OWNER=true

/etc/jitsi/meet/jitsi.my-domain.tld-config.js

var config = {
  hosts: {
    domain: 'jitsi.my-domain.tld',
    muc: 'conference.jitsi.my-domain.tld'
  },
  bosh: '//jitsi.my-domain.tld/http-bind',
  clientNode: 'http://jitsi.org/jitsimeet',
  testing: {
    p2pTestMode: false
  },
  enableNoAudioDetection: true,
  enableNoisyMicDetection: true,
  resolution: 720,
  constraints: {
    video: {
      height: {
        ideal: 720,
        max: 1080,
        min: 360
      }
    }
  },
  disableSimulcast: true,
  enableLayerSuspension: true,
  desktopSharingFrameRate: {
    min: 5,
    max: 25
  },
  channelLastN: -1,
  lastNLimits: {
    5:  10,
    10: 5,
    20: 2,
  },
  requireDisplayName: true,
  enableWelcomePage: true,
  defaultLanguage: 'de',
  disableProfile: true,
  enableFeaturesBasedOnToken: true,
  disableThirdPartyRequests: true,
  p2p: {
    enabled: false,
    stunServers: [
      { urls: 'stun:meet-jit-si-turnrelay.jitsi.net:443' }
    ]
  },
  analytics: {
  },
  deploymentInfo: {
  },
  doNotStoreRoom: true,
};

Is the attribute moderator inside the JWT really required and if yes, which component does it use where?

I create my token this way

$jwtPayload = array(
  ...
  "moderator" => $participation['is_moderator'],
  "context" => array(
    "user" => array(
      ...
      "affiliation" => $participation['is_moderator'] ? "owner" : "member",
    ),
    "features" => array(
      ...
    )
  )
);

I use that attribute, because @emrah used it in this post and other places. However, the example token of the school installer does not mention it.

token-moderation module uses the moderator field; token_affiliation module uses the affiliation field.

School installer doesn’t use token-moderation module, therefore no need the moderator field for it

Then let me ask: Do I need the token-moderation plugin?

I had a closer look on the source code of the modules token_affiliation and token_owner_party. First, let me stress that I do not know Lua nor have I ever scripted Prosody before. So it might be, that my observation a plainly wrong. However, I believe that the modules simply don’t do what they are supposed to do.

Basically, the module token_owner_party does two things:

  1. If a user is going to enter a room, the module checks if the token claims that the user is a moderator, owner or teacher. If the token does so, the user is let into to room. Otherwise, a user is only let into the room, if the room is already occupied by at least one other user.
  2. If a user leaves the room, the module checks if the user has been an ordinary participant. If so, nothing happens. (Because at least one moderator must still in the room.) If a moderator left the room, the modules checks if it does find another user who is a moderator. If not, a timer starts which kicks out all other users after the timer triggered. (I simplified the last part a little bit.)

However, what I do not see anywhere is any logic, assertions, checks or whatever which controls that only moderators are allowed to mute other participants, kick them out and alike.


@emrah Did you had a close look at my config files? Are there any obvious misconfigurations?

remove org.jitsi.jicofo.auth.URL=EXT_JWT:jitsi.my-domain.tld from /etc/jitsi/jicofo/sip-communicator.properties and restart the services.

The other things seem OK

And what is your prosody version?

There were actually two questions in my previous post, you only answered the second one :wink:

Also consider my explanation below that question.

Why? According to Secure Domain Setup it should be set, if token-based authentication is used. What exactly does this config option do?

# aptitude show prosody
Package: prosody                         
Version: 0.11.4-1
State: installed
Automatically installed: yes

A normal Ubuntu 20.04.1 LTS installation

After setting the log level to debug, I found the following entries in prosody.log

Jan 30 18:32:41 conference.jitsi.my-domain.tld:muc  debug   no occupant found for nocheintest@conference.jitsi.my-domain.tld/4ea0875a; creating new occupant object for 4ea0875a-c140-4c40-b2eb-53a7eba0e004@jitsi.my-domain.tld/H_oJwLSC
Jan 30 18:32:41 conference.jitsi.my-domain.tld:token_owner_party    debug   let the party begin
Jan 30 18:32:41 conference.jitsi.my-domain.tld:token_verification   debug   pre join: MUC room (nocheintest@conference.jitsi.my-domain.tld) <presence to='nocheintest@conference.jitsi.my-domain.tld/4ea0875a' from='4ea0875a-c140-4c40-b2eb-53a7eba0e004@jitsi.my-domain.tld/H_oJwLSC'><x xmlns='http://jabber.org/protocol/muc'/><stats-id>Ayla-0jM</stats-id><c hash='sha-1' ver='PlsZdq8nYYZ7tQJSmtZfoWftI5M=' xmlns='http://jabber.org/protocol/caps' node='http://jitsi.org/jitsimeet'/><email>John Doe@my-domain.tld</email><nick xmlns='http://jabber.org/protocol/nick'>Jon Doe</nick><audiomuted xmlns='http://jitsi.org/jitmeet/audio'>false</audiomuted><videoType xmlns='http://jitsi.org/jitmeet/video'>camera</videoType><videomuted xmlns='http://jitsi.org/jitmeet/video'>false</videomuted><x xmlns='vcard-temp:x:update'><photo/></x><identity><user><affiliation>owner</affiliation><name>Jon Doe</name><email>John Doe@my-domain.tld</email></user></identity></presence>
Jan 30 18:32:41 conference.jitsi.my-domain.tld:token_verification   debug   Session token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJqaXRzaSIsImlzcyI6ImppdHNpIiwic3ViIjoiaml0c2kubWhubmV0LmRlIiwibmJmIjoxNjExNTAyMTU1LCJpYXQiOjE2MTIwMjc5NTcsImV4cCI6MTYxMjExMDg1NSwicm9vbSI6Ik5vY2hFaW5UZXN0IiwibW9kZXJhdG9yIjp0cnVlLCJjb250ZXh0Ijp7InVzZXIiOnsibmFtZSI6Ik1hdHRoaWFzIE5hZ2VsIiwiZW1haWwiOiJtYXR0aGlhcy5oLm5hZ2VsQGhlcm1hbm4tZWhsZXJzLWtvbGxlZy5kZSIsImFmZmlsaWF0aW9uIjoib3duZXIifSwiZmVhdHVyZXMiOnsicmVjb3JkaW5nIjp0cnVlLCJsaXZlc3RyZWFtaW5nIjp0cnVlLCJzY3JlZW4tc2hhcmluZyI6dHJ1ZX19fQ.ku8KrRABUs1jjDqQBM5Um3C4M9rhl0yH_o0WRJzs2JA, session room: NochEinTest
Jan 30 18:32:41 conference.jitsi.my-domain.tld:token_verification   debug   Will verify token for user: 4ea0875a-c140-4c40-b2eb-53a7eba0e004@jitsi.my-domain.tld/H_oJwLSC, room: nocheintest@conference.jitsi.my-domain.tld/4ea0875a 
Jan 30 18:32:41 conference.jitsi.my-domain.tld:token_verification   debug   allowed: 4ea0875a-c140-4c40-b2eb-53a7eba0e004@jitsi.my-domain.tld/H_oJwLSC to enter/create room: nocheintest@conference.jitsi.my-domain.tld/4ea0875a

...

Jan 30 18:36:47 conference.jitsi.my-domain.tld:token_verification   debug   pre join: MUC room (nocheintest@conference.jitsi.my-domain.tld) <presence to='nocheintest@conference.jitsi.my-domain.tld/30c4e895' from='30c4e895-23d0-4070-8584-f6f3e433b61c@jitsi.my-domain.tld/hejlyO4K'><x xmlns='http://jabber.org/protocol/muc'/><stats-id>Malinda-xyJ</stats-id><c hash='sha-1' ver='PlsZdq8nYYZ7tQJSmtZfoWftI5M=' xmlns='http://jabber.org/protocol/caps' node='http://jitsi.org/jitsimeet'/><nick xmlns='http://jabber.org/protocol/nick'>Jane Doe</nick><audiomuted xmlns='http://jitsi.org/jitmeet/audio'>false</audiomuted><videoType xmlns='http://jitsi.org/jitmeet/video'>camera</videoType><videomuted xmlns='http://jitsi.org/jitmeet/video'>false</videomuted><x xmlns='vcard-temp:x:update'><photo/></x><identity><user><affiliation>member</affiliation><name>Jane Doe</name><email>userdata: (nil)</email></user></identity></presence>
Jan 30 18:36:47 conference.jitsi.my-domain.tld:token_verification   debug   Session token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJqaXRzaSIsImlzcyI6ImppdHNpIiwic3ViIjoiaml0c2kubWhubmV0LmRlIiwibmJmIjoxNjExNTAyMTU1LCJpYXQiOjE2MTIwMjgyMDMsImV4cCI6MTYxMjExMDg1NSwicm9vbSI6Ik5vY2hFaW5UZXN0IiwibW9kZXJhdG9yIjpmYWxzZSwiY29udGV4dCI6eyJ1c2VyIjp7Im5hbWUiOiJGYWJpZW5uZSBGaXNjaGVyIiwiZW1haWwiOm51bGwsImFmZmlsaWF0aW9uIjoibWVtYmVyIn0sImZlYXR1cmVzIjp7InJlY29yZGluZyI6ZmFsc2UsImxpdmVzdHJlYW1pbmciOmZhbHNlLCJzY3JlZW4tc2hhcmluZyI6ZmFsc2V9fX0.jWowp6ntovr097UFGWAkC_kqrHB-dgTqHXQYN6zTFsc, session room: NochEinTest
Jan 30 18:36:47 conference.jitsi.my-domain.tld:token_verification   debug   Will verify token for user: 30c4e895-23d0-4070-8584-f6f3e433b61c@jitsi.my-domain.tld/hejlyO4K, room: nocheintest@conference.jitsi.my-domain.tld/30c4e895 
Jan 30 18:36:47 conference.jitsi.my-domain.tld:token_verification   debug   allowed: 30c4e895-23d0-4070-8584-f6f3e433b61c@jitsi.my-domain.tld/hejlyO4K to enter/create room: nocheintest@conference.jitsi.my-domain.tld/30c4e895

...

Jan 30 18:40:21 conference.jitsi.my-domain.tld:token_owner_party    debug   an owner leaved, 4ea0875a-c140-4c40-b2eb-53a7eba0e004@jitsi.my-domain.tld/H_oJwLSC
Jan 30 18:40:21 conference.jitsi.my-domain.tld:token_owner_party    debug   an owner is here, 30c4e895-23d0-4070-8584-f6f3e433b61c@jitsi.my-domain.tld/hejlyO4K
  1. John Doe is authenticated successfully and is a owner (look out for <user>affiliation>owner</affiliation>). Jon Doe’s gets the internal ID H_oJwLSC.
  2. Jane Doe is authenticated successfully and is a member (look out for <user><affiliation>member</affiliation>. Jane Doe gets the internal ID hejlyO4K.
  3. John Doe leaves the conference (look out for an owner leaved ... H_oJwLSC)
  4. Jane Doe is considered to be the new owner (look out for an owner is here ... hejlyO4K)

The one million dollar question: Why is Jane Doe considered to be a moderator although authentication seem to be as expected?

Could you paste the output

ls -alh /usr/share/jitsi-meet/prosody-plugins/

First of all: Problem solved

If the module token_affiliation is used, the option org.jitsi.jicofo.auth.URL MUST NOT be set in etc/jitsi/jicofo/sip-communicator.properties.

After I had removed that option and had restarted the service, the problem vanished. Also, all other problems from my previous post disappeared, too. I also believe, that this is the same reason why the module token_affiliation does not work in a dockerized environment as described in this post and follow-ups (identical problem: all members unintendedly become owners, too).

As @slauth writes here, it might be helpful to shed some light on this issue:

  1. What is the URL org.jitsi.jicofo.auth.URL intended to do?
  2. How does it interact with the affiliation process of Prosody?
  3. Why does the guide on Secure Domain Setup says that it should be set for token-based authentication, if it is actually harmful?

Although, I am happy now that it basically works, I have some follow-up questions @emrah :wink:

  1. If a member is re-directed to the conference room with a JWT-url (i.e. https://jitsi.my-domain.tld/SomeRoomName?jwt=an-encoded-jwt-here) and the room does not exist yet, the user is redirected to a static error page which tells the user that the user is not authorized (/static/authError.html)

  2. If a user is kicked out by a moderator, then the user is disconnected from the conference and the screen simply turns gray, but the user’s browser remains on the page of the conference (e.g. https://jitsi.my-domain.tld/SomeRoomName).

  3. If a user tries to enter a room by directly typing the URL, but without the JWT-part (e.g. https://jitsi.my-domain.tld/SomeRoomName), then Jitsi Meet asks for a username and password. Of course, password-based authentication is unsuccessful and the dialog re-appears endlessly. The user is caught in an infinite trap.

Trivial question: Is it possible, to redirect the user back to the authentication page which generates the JWT in all of the three cases, such that the user can try to re-login?

1 Like

I have only a solution for the first one now

/static/authError.html

<html>
<head>
    <!--#include virtual="/base.html" -->
    <link rel="stylesheet" href="css/all.css"/>
    <!--#include virtual="/title.html" -->
    <meta http-equiv="refresh" content="5; URL=https://your.authentication.page/" />
</head>
<body>
    <div class="redirectPageMessage">Sorry! You are not allowed to be here :(</div>
</body>
</html>

for second and third /usr/share/jitsi-meet/body.html

<script>                                        
function redirect() {                           
    window.location.href="https://community.jitsi.org/";
}                                               
                                                
function subscribeToEvents() {                  
    try {                                       
        if (!APP.store.getState()) {            
            throw new Error("state is not ready. try again");
        } else if (!APP.store.getState()["features/base/connection"]) {
            throw new Error("connection is not ready. try again");
        }                                       
                                                
        cnn = APP.store.getState()["features/base/connection"];
        if (cnn.error) {                        
            return redirect();                  
        }                                       
                                                
        APP.conference._room.on("conference.kicked", redirect);
        APP.conference._room.on("conference.left", redirect);
    } catch(e) {                                
        setTimeout(() => subscribeToEvents(), 3000);
    }                                           
}                                               
                                                
subscribeToEvents();                            
</script> 

I am trying to install Jitsi tokens on Debian 10. But I get error when I run this:
sudo apt-get install jitsi-meet-tokens
“Error: Failed installing dependency: https://luarocks.org/luaossl-20200709-0.src.rock - Build error: Failed compiling object src/openssl.o
Failed to install luajwtjitsi - try installing it manually”

Before that I did these steps
1 echo ‘deb Index of /debian-security stretch/updates main’ >> sudo /etc/apt/sources.list.d/stretch.list
2 sudo apt-get update
3. sudo apt-get install libssl1.0
3.1 sudo wget https://emrah.com/files/lua-cjson-2.1devel-1.linux-x86_64.rock
4. sudo apt-get install luarocks liblua5.2-dev
5. sudo luarocks install lua-cjson-2.1devel-1.linux-x86_64.rock

What am I doing wrong?

My goal is: I need to enable Jitsi token authentication on my self hosted jitsi because I am trying to use Jitsi from Drupal using this module:

Any advice how to use jitsi with jwt token and drupal so that the jitsi server accept users only from the Drupal site with that token

You don’t need to install lua-cjson-2.1devel-1.linux-x86_64.rock manually anymore. This is already fixed on the jitsi-meet-tokens package. Install only this package