SOLVED JWT: Invalid Signature

I was following various setups to get JWT going, including the Jitsi Tokens page, and such as https://doganbros.com. I have read other posts, and the solutions don’t seem to apply to my case. I am in need of another set of eyes most likely. Thanks ahead of time for looking at this.

Here are the contents of /etc/prosody/conf.d/mydomain.com.cfg.lua:
plugin_paths = { "/usr/share/jitsi-meet/prosody-plugins/" }

-- domain mapper options, must at least have domain base set to use the mapper
muc_mapper_domain_base = "mydomain.com";

turncredentials_secret = "pc2WaWmT93PcyUta";

turncredentials = {
  { type = "stun", host = "mydomain.com", port = "3478" },
  { type = "turn", host = "mydomain.com", port = "3478", transport = "udp" },
  { type = "turns", host = "mydomain.com", port = "5349", transport = "tcp" }
};

cross_domain_bosh = true; -- so that it accepts from other domains
consider_bosh_secure = true;
-- https_ports = { }; -- Remove this line to prevent listening on port 5284

-- https://ssl-config.mozilla.org/#server=haproxy&version=2.1&config=intermediate&openssl=1.1.0g&guideline=5.4
ssl = {
  protocol = "tlsv1_2+";
  ciphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE
-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"
}

VirtualHost "mydomain.com"
        -- enabled = false -- Remove this line to enable this host
        authentication = "token"
        -- Properties below are modified by jitsi-meet-tokens package config
        -- and authentication above is switched to "token"
        app_id="WHGLsfAmXN6YqNiNGFCii72lJkXvfibN8JcExd84m90FUiVBgpeuUfAwIqnI"
        app_secret="lKAQrSVAJUL17s3TuJ5c1qXpC9y6xLAGuKvAB0eLYj773nOoZPOZriW80TxT"
        -- Assign this host a certificate for TLS, otherwise it would use the one
        -- set in the global section (if any).
        -- Note that old-style SSL on port 5223 only supports one certificate, and will always
        -- use the global one.
        ssl = {
                key = "/etc/nginx/acmecerts/mydomain.com/mydomain.com.key";
                certificate = "/etc/nginx/acmecerts/mydomain.com/fullchain.cer";
        }
        speakerstats_component = "speakerstats.mydomain.com"
        conference_duration_component = "conferenceduration.mydomain.com"
        -- we need bosh
        modules_enabled = {
            "bosh";
            "pubsub";
            "ping"; -- Enable mod_ping
            "speakerstats";
            "turncredentials";
            "conference_duration";
            "muc_lobby_rooms";
            "presence_identity";
        }
        c2s_require_encryption = true;
        lobby_muc = "lobby.mydomain.com"
        main_muc = "conference.mydomain.com"
        -- muc_lobby_whitelist = { "recorder.mydomain.com" } -- Here we can whitelist jibri to enter lobby enabled rooms

        allow_empty_token = false;

Component "conference.mydomain.com" "muc"
    storage = "memory"
    modules_enabled = {
        "muc_meeting_id";
        "muc_domain_mapper";
        "token_verification";
    }
    admins = { "focus@auth.mydomain.com" }
    muc_room_locking = false
    muc_room_default_public_jids = true

-- internal muc component
Component "internal.auth.mydomain.com" "muc"
    storage = "memory"
    modules_enabled = {
      "ping";
    }
    admins = { "focus@auth.mydomain.com", "jvb@auth.mydomain.com" }
    muc_room_locking = false
    muc_room_default_public_jids = true

VirtualHost "auth.mydomain.com"
    ssl = {
        key = "/etc/prosody/certs/auth.mydomain.com.key";
        certificate = "/etc/prosody/certs/auth.mydomain.com.crt";
    }
    authentication = "internal_plain"

Component "focus.mydomain.com"
    component_secret = "4EkCvneH"

Component "speakerstats.mydomain.com" "speakerstats_component"
    muc_component = "conference.mydomain.com"

Component "conferenceduration.mydomain.com" "conference_duration_component"
    muc_component = "conference.mydomain.com"

Component "lobby.mydomain.com" "muc"
    storage = "memory"
    restrict_room_creation = true
    muc_room_locking = false
    muc_room_default_public_jids = true


VirtualHost "guest.mydomain.com"
        authentication = "token"
        app_id="WHGLsfAmXN6YqNiNGFCii72lJkXvfibN8JcExd84m90FUiVBgpeuUfAwIqnI"
        app_secret="lKAQrSVAJUL17s3TuJ5c1qXpC9y6xLAGuKvAB0eLYj773nOoZPOZriW80TxT"

        c2s_require_encryption = true;
        allow_empty_token = false;
My token is structured like this:
    {
        'typ' => 'JWT',
        'alg' => 'HS256'
    }

   {
        'context' => {
            'user' => {
                'name' => 'My name',
            },
        },
        'iss' => 'WHGLsfAmXN6YqNiNGFCii72lJkXvfibN8JcExd84m90FUiVBgpeuUfAwIqnI',
        'aud' => 'WHGLsfAmXN6YqNiNGFCii72lJkXvfibN8JcExd84m90FUiVBgpeuUfAwIqnI',
        'sub' => 'mydomain.com,
        'room' => '*',
        'exp' => 1606172963, (about an hour from now)

    };

I verified signature with jwt.io.

I restarted Prosody and then went to the without any token, the logs show a bouncing error and at the end it notes token is required, as expected:
Nov 23 16:46:32 startup info    Hello and welcome to Prosody version 0.11.7
Nov 23 16:46:32 startup info    Prosody is using the select backend for connection handling
Nov 23 16:46:32 portmanager     info    Activated service 'component' on [0.0.0.0]:5347
Nov 23 16:46:32 focus.mydomain.com:tls    info    Certificates loaded
Nov 23 16:46:32 portmanager     info    Activated service 's2s' on [::]:5269, [*]:5269
Nov 23 16:46:32 internal.auth.mydomain.com:tls    info    Certificates loaded
Nov 23 16:46:32 conference.mydomain.com:muc_domain_mapper info    Loading mod_muc_domain_mapper for host internal.auth.mydomain.com!
Nov 23 16:46:32 conference.mydomain.com:muc_domain_mapper info    Loading mod_muc_domain_mapper for host conference.mydomain.com!
Nov 23 16:46:32 conference.mydomain.com:muc_domain_mapper info    Loading mod_muc_domain_mapper for host focus.mydomain.com!
Nov 23 16:46:32 conference.mydomain.com:tls       info    Certificates loaded
Nov 23 16:46:32 auth.mydomain.com:tls     info    Certificates loaded
Nov 23 16:46:32 portmanager     info    Activated service 'c2s' on [::]:5222, [*]:5222
Nov 23 16:46:32 portmanager     info    Activated service 'legacy_ssl' on no ports
Nov 23 16:46:32 conference.mydomain.com:muc_domain_mapper info    Loading mod_muc_domain_mapper for host auth.mydomain.com!
Nov 23 16:46:32 meet.mydomain:tls  info    Certificates loaded
Nov 23 16:46:32 portmanager     info    Activated service 'http' on [::]:5280, [*]:5280
Nov 23 16:46:32 portmanager     error   Error binding encrypted port for https: No certificate present in SSL/TLS configuration for https port 5281
Nov 23 16:46:32 portmanager     error   Error binding encrypted port for https: No certificate present in SSL/TLS configuration for https port 5281
Nov 23 16:46:32 portmanager     info    Activated service 'https' on no ports
Nov 23 16:46:32 conference.mydomain.com:muc_domain_mapper info    Loading mod_muc_domain_mapper for host mydomain.com!
Nov 23 16:46:32 guest.mydomain.com:tls    info    Certificates loaded
Nov 23 16:46:32 conference.mydomain.com:muc_domain_mapper info    Loading mod_muc_domain_mapper for host guest.mydomain.com!
Nov 23 16:46:32 localhost:tls   info    Certificates loaded
Nov 23 16:46:33 conference.mydomain.com:muc_domain_mapper info    Loading mod_muc_domain_mapper for host localhost!
Nov 23 16:46:33 lobby.mydomain.com:tls    info    Certificates loaded
Nov 23 16:46:33 conference.mydomain.com:muc_domain_mapper info    Loading mod_muc_domain_mapper for host lobby.meet.gbtechlab.com!
Nov 23 16:46:33 mydomain.com:muc_lobby_rooms      info    Lobby component loaded lobby.mydomain.com
Nov 23 16:46:33 general info    Starting conference duration timer for conference.mydomain.com
Nov 23 16:46:33 conferenceduration.mydomain.com:conference_duration_component     info    Hook to muc events on conference.mydomain.com
Nov 23 16:46:33 conferenceduration.mydomain.com:tls       info    Certificates loaded
Nov 23 16:46:33 conference.mydomain.com:muc_domain_mapper info    Loading mod_muc_domain_mapper for host conferenceduration.mydomain.com!
Nov 23 16:46:33 general info    Starting speakerstats for conference.mydomain.com
Nov 23 16:46:33 speakerstats.mydomain.com:speakerstats_component  info    Hook to muc events on conference.mydomain.com
Nov 23 16:46:33 speakerstats.mydomain.com:tls     info    Certificates loaded
Nov 23 16:46:33 conference.mydomain.com:muc_domain_mapper info    Loading mod_muc_domain_mapper for host speakerstats.mydomain.com!
Nov 23 16:46:34 conference.mydomain.com:muc_domain_mapper warn    Session filters applied
Nov 23 16:46:34 c2s55fd8b1cf130 info    Client connected
Nov 23 16:46:35 conference.mydomain.com:muc_domain_mapper warn    Session filters applied
Nov 23 16:46:35 c2s55fd8b1e3ea0 info    Client connected
Nov 23 16:46:36 c2s55fd8b1cf130 info    Stream encrypted (TLSv1.2 with ECDHE-RSA-AES128-GCM-SHA256)
Nov 23 16:46:36 c2s55fd8b1cf130 info    Authenticated as focus@auth.mydomain.com
Nov 23 16:46:36 focus.mydomain.com:component      warn    Component not connected, bouncing error for: <iq from='focus@auth.mydomain.com/focus18218942984945' type='get' to='focus.mydomain.com' id='DLapm-36'>
Nov 23 16:46:36 c2s55fd8b1e3ea0 info    Stream encrypted (TLSv1.2 with ECDHE-RSA-AES128-GCM-SHA256)
Nov 23 16:46:37 c2s55fd8b1e3ea0 info    Authenticated as jvb@auth.mydomain.com
Nov 23 16:46:37 jcp55fd8b09c230 info    Incoming Jabber component connection
Nov 23 16:46:37 focus.mydomain.com:component      info    External component successfully authenticated
Nov 23 16:47:17 mod_bosh        info    Client tried to use sid 'ade75464-b640-4553-8cd6-9c7c0c07be99' which we don't know about
Nov 23 16:47:23 conference.mydomain.com:muc_domain_mapper warn    Session filters applied
Nov 23 16:47:23 mod_bosh        info    New BOSH session, assigned it sid '97a0f07f-79af-4638-b8cf-861a1a2e78a7'
Nov 23 16:47:24 general warn    Error verifying token err:not-allowed, reason:token required
Then I attached my jwt to the end of a room url with with ?jwt=mytoken and the log shows:
Nov 23 16:48:41 mod_bosh        info    Client tried to use sid '97a0f07f-79af-4638-b8cf-861a1a2e78a7' which we don't know about
Nov 23 16:48:48 conference.meet.gbtechlab.com:muc_domain_mapper warn    Session filters applied
Nov 23 16:48:48 mod_bosh        info    New BOSH session, assigned it sid 'a5f2f654-7019-4b96-ba03-879a96b15356'
Nov 23 16:48:48 general warn    Error verifying token err:not-allowed, reason:Invalid signature
In the browser I am getting:

In the console:
[connection.js] CONNECTION FAILED: connection.passwordRequired

And in the UI "Authentication Failed"

What is causing the token to not be accepted? Ultimately, I would like for moderators and guests to access only via token and no access without a token.

Thank you for taking a look at the configuration.

EDIT: additional info:

  • Calling from VueJS, using lyno/lib-jitsi-meet (though response to app and response in GUI with direct access is the same)
  • Jitsi-Meet is running on updated Ubuntu 18.04 and I purged and reinstalled Jitsi-Meet which worked fine before attempting to add token config

Well, the token signature WAS invalid after all - somehow it was showing as validated on jwt.io when I pasted in the secret for testing…but the server was creating a token with a completely different secret.

I accomplished the end goal of room entry (and appearance of room entry) by all participants requiring tokens with the following settings in mydomain.com.cfg.lua:
plugin_paths = { "/usr/share/jitsi-meet/prosody-plugins/" }

-- domain mapper options, must at least have domain base set to use the mapper
muc_mapper_domain_base = "mydomain.com";

turncredentials_secret = "zzzzzzzzzzzzzz";

turncredentials = {
  { type = "stun", host = "mydomain.com", port = "3478" },
  { type = "turn", host = "mydomain.com", port = "3478", transport = "udp" },
  { type = "turns", host = "mydomain.com", port = "5349", transport = "tcp" }
};

cross_domain_bosh = true;
consider_bosh_secure = true;
-- https_ports = { }; -- Remove this line to prevent listening on port 5284

-- https://ssl-config.mozilla.org/#server=haproxy&version=2.1&config=intermediate&openssl=1.1.0g&guideline=5.4
ssl = {
  protocol = "tlsv1_2+";
  ciphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"
}

VirtualHost "mydomain.com"
        -- enabled = false -- Remove this line to enable this host
        authentication = "token"
        -- Properties below are modified by jitsi-meet-tokens package config
        -- and authentication above is switched to "token"
        app_id="zzzzzzzzzzzzzzzz"
        app_secret="zzzzzzzzzzzzzzzzz"
        allow_empty_token=false
        -- Assign this host a certificate for TLS, otherwise it would use the one
        -- set in the global section (if any).
        -- Note that old-style SSL on port 5223 only supports one certificate, and will always
        -- use the global one.
        ssl = {
                key = "/etc/prosody/certs/mydomain.com.key";
                certificate = "/etc/prosody/certs/mydomain.com.crt";
        }
        speakerstats_component = "speakerstats.mydomain.com"
        conference_duration_component = "conferenceduration.mydomain.com"
        -- we need bosh
        modules_enabled = {
            "bosh";
            "pubsub";
            "ping"; -- Enable mod_ping
            "speakerstats";
            "turncredentials";
            "conference_duration";
            "muc_lobby_rooms";
            "presence_identity";
            "token_verification";
        }
        c2s_require_encryption = false
        lobby_muc = "lobby.mydomain.com"
        main_muc = "conference.mydomain.com"
        -- muc_lobby_whitelist = { "recorder.mydomain.com" } -- Here we can whitelist jibri to enter lobby enabled rooms

Component "conference.mydomain.com" "muc"
    storage = "memory"
    modules_enabled = {
        "muc_meeting_id";
        "muc_domain_mapper";
        "token_verification";
    }
    admins = { "focus@auth.mydomain.com" }
    muc_room_locking = false
    muc_room_default_public_jids = true

-- internal muc component
Component "internal.auth.mydomain.com" "muc"
    storage = "memory"
    modules_enabled = {
      "ping";
    }
    admins = { "focus@auth.mydomain.com", "jvb@auth.mydomain.com" }
    muc_room_locking = false
    muc_room_default_public_jids = true

VirtualHost "auth.mydomain.com"
    ssl = {
        key = "/etc/prosody/certs/auth.mydomain.com.key";
        certificate = "/etc/prosody/certs/auth.mydomain.com.crt";
    }
    authentication = "internal_plain"

Component "focus.mydomain.com"
    component_secret = "zzzzzzzzzzzz"

Component "speakerstats.mydomain.com" "speakerstats_component"
    muc_component = "conference.mydomain.com"

Component "conferenceduration.mydomain.com" "conference_duration_component"
    muc_component = "conference.mydomain.com"

Component "lobby.mydomain.com" "muc"
    storage = "memory"
    restrict_room_creation = true
    muc_room_locking = false
    muc_room_default_public_jids = true

VirtualHost "guest.meet.gbtechlab.com"
    authentication = "token"
    app_id = "zzzzzzzzzzzzzzzzzz";
    app_secret = "zzzzzzzzzzzzzzzzzzzz";
    enabled_modules = {
        "token_verification";
    }
    c2s_require_encryption = true;

    allow_empty_token = false;