Jitsi JWT authentication not working

Short answer

Find whether the failure is token delivery, signature/claims, room admission or role assignment. Inspect tokens locally without printing private payloads, compare the actual server configuration, and test a short-lived token for one authorized room. Do not fix a mismatch by granting every room or disabling authentication.

Start by finding where the request fails. A readable token is not necessarily valid, and a valid signature does not prove that its room or moderator permissions match your policy. Use the JWT setup guide for a new issuer; use this page for an existing failure. [S2][S3][S4][S5]

Source scope: reviewed 24 October 2026 against docker-jitsi-meet stable-11248-2, commit 74d3e6e, and Jitsi Meet 11248 source at f765042b. We tested the local inspection and log-filter examples with synthetic data. We have not reproduced every failure on a live server; this revision does not extend the older stable-11031 production baseline. [S1][S2][S3]

1. Identify the failing stage

Symptom Inspect next
Login prompt or no token seen Correct server, complete token delivery and active authentication mode
Token rejected before room entry Format, algorithm, signature, issuer, audience and time claims
Token validates but one room fails Literal room, domain/tenant mapping and room admission policy
Guests enter unexpectedly or cannot enter Anonymous domain, empty-token allowance and host/lobby policy
Participant becomes a moderator Affiliation module and other owner policies
Call disconnects after joining Establish whether auth failed; also investigate transport and other components

These are diagnostic branches, not proof of a cause. The reviewed authentication and room modules perform different checks. A generic disconnect can require connection troubleshooting. [S2][S3][S4][S5]

Record the running component image references or package versions first using version inventory. A tutorial’s defaults do not establish your installed configuration. [S1]

2. Inspect a token locally without exposing its payload

Save the following as inspect_jitsi_jwt.py on your own machine. Run it as a file in a normal terminal; do not run it through a heredoc that also consumes standard input. The helper uses Python’s standard library and makes no network requests. It supports a compact three-segment token and has a 32 KiB inspection limit, which is a helper limit, not a Jitsi protocol limit. [S6][S7]

Python
import base64
import binascii
import getpass
import json
import math
import re
import time
import warnings


def inspect_token(token, now=None):
    token = token.strip()
    parts = token.split(".")
    if len(token) > 32768 or len(parts) != 3 or any(
        not re.fullmatch(r"[A-Za-z0-9_-]+", part) for part in parts
    ):
        raise ValueError("Unsupported token format")

    def read_json(part):
        raw = base64.b64decode(part + "=" * (-len(part) % 4),
                               altchars=b"-_", validate=True)
        value = json.loads(raw.decode("utf-8"))
        if not isinstance(value, dict):
            raise ValueError("Expected JSON object")
        return value

    header, payload = read_json(parts[0]), read_json(parts[1])
    now = time.time() if now is None else now

    def time_state(name):
        if name not in payload:
            return "missing"
        value = payload[name]
        if type(value) not in (int, float):
            return "invalid"
        try:
            if not math.isfinite(value):
                return "invalid"
        except OverflowError:
            return "invalid"
        if name == "exp":
            return "expired" if value <= now else "future"
        return "not-yet-valid" if value > now else "active"

    alg = header.get("alg")
    return {
        "signature_verified": False,
        "algorithm": alg if alg in ("HS256", "HS384", "HS512", "RS256", "RS384", "RS512") else "other",
        "typ_is_JWT": header.get("typ") == "JWT",
        "string_claims": {name: isinstance(payload.get(name), str)
                          for name in ("iss", "aud", "sub", "room")},
        "room_is_wildcard": payload.get("room") == "*",
        "subject_is_wildcard": payload.get("sub") == "*",
        "exp_state": time_state("exp"),
        "nbf_state": time_state("nbf"),
    }


def main():
    try:
        with warnings.catch_warnings():
            warnings.simplefilter("error", getpass.GetPassWarning)
            token = getpass.getpass("JWT (hidden, local inspection): ")
        print(json.dumps(inspect_token(token), indent=2))
    except (ValueError, UnicodeError, binascii.Error,
            getpass.GetPassWarning, EOFError):
        raise SystemExit("Cannot inspect: check token format and use a terminal with hidden input.")


if __name__ == "__main__":
    main()

Run it from the directory where you saved the file: [S6]

Terminal
python3 inspect_jitsi_jwt.py

On Windows, use python inspect_jitsi_jwt.py if that is your Python launcher. The helper stops if hidden input is unavailable. Do not place the token in a shell argument or print it to work around a terminal problem. [S6]

The report intentionally omits claim values and user details. signature_verified: false means this helper has not checked the signature; future is a local time observation, not an acceptance result. Compare the actual issuer, audience, subject and room privately in your trusted backend and server configuration. Do not paste a real token into an online decoder. [S2][S3][S7]

3. Compare claims with the active configuration

Field Check Correction to investigate
alg and signature Configured algorithm and the intended signing key Align issuer and verifier; do not accept an algorithm just because the token requests it
iss Actual accepted issuer list Correct environment or issuer configuration
aud Actual accepted audience list Use the scalar audience expected by this pinned verifier and the setup example
sub Present; domain mapping when verification is enabled Use the configured domain for the intended deployment
room Intended room and literal/pattern mode Issue a new authorized room-specific token
exp Numeric Unix seconds and server time Fix the issuer’s lifetime and clock; do not drop expiry to hide failure
nbf, if used Numeric Unix seconds, not in the future when admission is intended Correct the intended activation time or clock; do not automatically backdate tokens

The reviewed JWT library compares issuer/audience claims with its configured lists; do not assume another library’s array or wildcard behavior transfers unchanged. It checks exp and nbf when those claims are present. A missing expiry is an issuer-policy problem even if a verifier permits it. Use the signing helpers to calculate a short lifetime after authorization. [S2][S3][S7]

Docker domain and room checks

The reviewed Docker template defaults domain verification off and XMPP_DOMAIN to meet.jitsi. Our setup guide explicitly enables domain verification. For a single-domain setup with that check on, use the actual main XMPP domain as sub; it can differ from the public meeting hostname. Tenant setups require their own mapping review. [S1][S2]

The pinned room utility lowercases literal room claims. Use one consistent room convention and verify the actual room/tenant mapping; do not diagnose every room failure as letter case. Pattern claims and older implementations need separate checks. [S2]

Do not use a wildcard test token as proof that your issuer works. It broadens access and can hide the exact mismatch being investigated. Issue a short-lived token through your backend for one disposable authorized room. Verify that it works there and is denied for a different room. Do not put a signing secret into a pasted command or bypass the application’s membership check. [S2][S7]

4. Read error categories without copying sensitive log lines

The pinned room verifier can log a full token on a rejected join. Its startup debug message can include the configured app secret. Other messages can include room or user information. Inspect logs privately and avoid turning on verbose production logging as a first response. Removing a signature from a JWT does not remove its payload. [S2][S4][S7]

For Docker, use the same project and Compose options as the running deployment. This pipeline prints only allowlisted constant phrases from the last 100 Prosody log lines, not the matching full lines. The grep expression is a local reporting filter; it does not alter the server. [S2][S3][S4][S8]

Terminal
docker compose logs --no-color --tail 100 prosody 2>/dev/null |
  grep -oF \
    -e 'token required' \
    -e 'Invalid signature' \
    -e 'Invalid or incorrect alg' \
    -e 'Not acceptable by exp' \
    -e 'Not acceptable by nbf' \
    -e "invalid 'iss' claim" \
    -e "invalid 'aud' claim" \
    -e 'room claim is missing' \
    -e 'sub claim is missing' \
    -e 'Room and token mismatched' |
  sort -u

No output does not prove that authentication succeeded: there may be no matching recent entry, a different component/version, missing logs or a command failure. This intentionally suppresses command-error output; inspect command failures locally if needed, without posting raw logs. The filter is not a general log sanitizer. [S8]

Error-to-check reference

These are exact phrases or prefixes in the pinned source. Installed versions may differ. Do not infer one certain root cause from the generic not-allowed response. [S2][S3][S4]

Phrase Check next
token required Token delivery and intended empty-token policy
Invalid signature Same intended key, intact token and matching algorithm; distinguish malformed signature encoding from failed verification
Invalid or incorrect alg Header algorithm versus configured verifier algorithm
Not acceptable by exp Expiry, seconds units and issuer/server clocks
Not acceptable by nbf Activation time and clocks
invalid 'iss' claim / invalid 'aud' claim Accepted list and actual claim values
room claim is missing / sub claim is missing Issuer payload and configured requirements
Room and token mismatched Domain/room mapping, including active verification settings

For missing-key or key-fetch errors, inspect the configured HS shared-secret or asymmetric key-service mode; they are different integrations. Do not copy RS256 settings into an HS256 setup or loosen accepted claims merely to suppress an error. [S2]

5. Token delivery, configuration changes and clocks

  • Login prompt: confirm the correct server, a complete token in the jwt IFrame API option or intended JWT URL parameter, and the active token authentication mode. Do not share the full URL. [S1][S2][S9]
  • Changed .env but no effect: preserve the existing project, environment files, profiles and overlays, then recreate changed services in a maintenance window. docker compose up -d applies changed Compose configuration; restart alone does not. Do not regenerate internal service passwords. [S1][S10]
  • Time failures: compare the issuer and server clocks. Correct synchronization and issue a new authorized token with intended exp/nbf values. Milliseconds can produce an excessively long expiry, so a future timestamp alone does not establish a sensible lifetime. [S3][S7]
  • Expiry after joining: test fresh join and reconnect separately. JWT expiry is not a guaranteed timer that ends an established meeting. Refresh tokens only after repeating application authorization. [S2][S3]

6. Guests and moderator permissions

ENABLE_GUESTS controls an anonymous guest domain; JWT_ALLOW_EMPTY controls empty-token acceptance on the token host. Also inspect custom allowlists and modules. The reviewed verifier has exceptions for administrative or allowlisted identities; ordinary participant acceptance needs its own test. [S1][S2][S4]

For the setup guide’s all-participants-token policy, verify both ENABLE_GUESTS=0 and JWT_ALLOW_EMPTY=0, then test a separate tokenless browser. If guests are intentional, test admission before and after host arrival and with the lobby enabled. Flags alone do not prove host-waiting behavior. [S1][S2][S11]

An accepted participant token does not establish correct role enforcement. Check the active token-affiliation module, its supported claims and other automatic owner policies. In the reviewed affiliation source, an owner-like affiliation can grant ownership even when a moderator field is false. Follow the moderator diagnostic checklist and test participant-first, host-rejoin and lobby cases separately. [S5]

Debian and Ubuntu package differences

Package installations do not read Docker .env inputs. Inspect the main Prosody virtual host, commonly /etc/prosody/conf.avail/meet.example.com.cfg.lua, and the web configuration, commonly /etc/jitsi/meet/meet.example.com-config.js. Replace the example with the actual host. Current upstream package guidance describes token authentication and conference modules with a different Jicofo arrangement from Docker; use it instead of transplanting Docker settings. [S11]

Use private local service logs and the installed package’s configuration. The same constant error categories may help, but versions and module wiring differ. A service restart is an operational change, so plan it after identifying a configuration fix rather than issuing it as a read-only diagnostic. [S3][S11]

Verify the correction and prevent repeats

After correcting the evidenced mismatch in a non-production test setup:

  1. Issue a token through the real authorized backend for one disposable room.
  2. Confirm the intended user enters that room and cannot use the token for another room.
  3. Test altered signatures, wrong issuer/audience and expired tokens on fresh connections.
  4. Check tokenless access, host/participant permissions and lobby behavior independently.
  5. Repeat fresh join and reconnect after token refresh, without granting broader access.

These are proposed acceptance checks, not results from your server. Keep a record of component versions, intended claim policy and sanitized error categories. Add issuer tests for the corrected failure so the next deployment does not silently reintroduce it. [S2][S3][S4][S5]

Open questions and maintenance

The local decoder and constant-only log filter were tested with synthetic tokens and logs. We checked error text and policy branches in pinned upstream source. No real token, credential, account or server configuration was changed or submitted, and no full Prosody admission, browser call, moderator or package test was completed for this revision.

Recheck after JWT-library, Docker-template or role-module changes. If you need help, share sanitized versions, setting names and constant error phrases through integration support, not a token with its signature removed. For broader connection failures, continue with disconnect diagnostics.

Sources

Reviewed 24 October 2026; source snapshots do not attest to every installed image.

Written by the Jitsi Help engineering team

Jitsi infrastructure engineers who deploy and run Jitsi Meet servers in production. Last updated . How we write our guides

Stuck on something this page does not cover? Ask a question below. Jitsi Help engineers read every one.

Did this fix it?

Something on this page is outdated or wrong?

Collect debug info

These commands gather what an engineer needs to diagnose a Jitsi server. Copy them, run them, and keep the output.

Terminal
# Run on the server, from your docker-jitsi-meet folder

# 1. Every service should be "running" (or "healthy")
docker compose ps

# 2. Which images and release tags are running
docker compose images

# 3. Recent logs from the core services
docker compose logs --tail=200 web prosody jicofo jvb

# 4. The settings most fixes depend on
grep -E '^(PUBLIC_URL|JVB_ADVERTISE_IPS|ENABLE_LETSENCRYPT|ENABLE_AUTH|AUTH_TYPE)=' .env

# 5. Is anything listening for media on UDP 10000?
sudo ss -ulnp | grep 10000

Installed with the Debian packages instead of Docker? Read the service logs with:

Terminal
sudo journalctl -u prosody -u jicofo -u jitsi-videobridge2 --since "1 hour ago"

Remove passwords, secrets and tokens before you share any output.

Send the output to an engineer

Frequently asked questions

How can I inspect a JWT without sending it to a website?

Run the local Python helper below from a saved file. It accepts the token through a hidden terminal prompt and reports selected field types and time status without printing the token, room name or user details. Decoding does not verify the signature or establish access.

Why does Jitsi ask for login when I pass a token?

Check that the full token reaches the intended server through the JWT URL parameter or IFrame API option, and that the running configuration uses token authentication. A login prompt alone does not prove that the signature or claims are valid.

Does a token with room and sub set to a wildcard prove my setup works?

No. Wildcards broaden access and can hide a room or domain mismatch. Use a short-lived token for one authorized room and the intended subject, then verify that a different room is denied.

Does ENABLE_GUESTS=0 always require a token from everyone?

Check the full configuration. The anonymous guest domain and JWT_ALLOW_EMPTY are separate mechanisms, and custom allowlists or modules can affect access. Test a tokenless session against the actual policy.

What is safe to share when asking for help?

Share component versions, sanitized setting names, the failure stage and the exact constant error phrase. Remove tokens, signatures, secrets, private room links and user details. Removing only a token's signature does not remove its private payload.

Discussion

    Stuck, or would rather not do this by hand?

    Deploy it in one click

    A private Jitsi server in your own AWS account with SSL, your domain and optional recording, transcription and JWT. Free 15 minute trial.

    Start free trial

    Talk to a Jitsi engineer

    Setup, fixes, branding, recording, scaling. Tell us what is happening and we reply with a plan and a quote.

    Get expert help

    Related

    Recently updated