Jitsi Meet JWT authentication

Short answer

Configure JWT authentication in Docker, then issue short-lived tokens only after your backend checks the signed-in user's access to the requested meeting. Match issuer, audience, room and domain settings. Token validity, anonymous guest access and moderator assignment require separate checks.

JWT lets your application issue signed meeting access credentials. The backend must first decide whether the user may enter that meeting; signing a browser-supplied room or moderator request without that check creates an access-control problem. [S3][S6][S7]

Scope: source reviewed on 24 October 2026 against docker-jitsi-meet stable-11248-2, commit 74d3e6e, and Jitsi Meet 11248 source at f765042b. The Node.js and Python signing examples were exercised locally. This revision is not a full live Jitsi authentication or moderator-policy test and does not extend the older stable-11031 production baseline. [S1][S3]

1. Choose the access policy before enabling JWT

This example uses one self-hosted server, HS256 with a shared secret, literal room names and a token for every participant. It does not configure JaaS, multiple tenants, regular-expression room claims or an external public-key service. Those modes need their own issuer and verifier configuration. [S2][S3]

Back up your existing project and persistent configuration, then plan a maintenance window. In the Docker project’s .env, replace the secret placeholder and confirm the main XMPP domain: [S1][S2]

Config
ENABLE_AUTH=1
AUTH_TYPE=jwt
JWT_APP_ID=my_app
JWT_APP_SECRET=REPLACE_WITH_A_PRIVATE_RANDOM_SECRET
JWT_ACCEPTED_ISSUERS=my_app
JWT_ACCEPTED_AUDIENCES=my_app
JWT_SIGN_TYPE=HS256
JWT_ENABLE_DOMAIN_VERIFICATION=1
JWT_ALLOW_EMPTY=0
ENABLE_GUESTS=0

For a new dedicated JWT secret, generate 32 random bytes as 64 hexadecimal characters. Store the result privately in the Jitsi configuration and your backend’s secret store; do not commit it or show it in a screenshot. Both sides must use the same character string, without hex-decoding it on one side only. This is a JWT secret, not a reason to regenerate existing internal service passwords. [S1][S2][S6][S7][S11]

Terminal
openssl rand -hex 32

Apply the changed configuration with the same project, environment files, profiles and overlays used by the existing deployment. For a project normally started without extra options: [S10]

Terminal
docker compose up -d

This may interrupt meetings. Compose recreates changed containers; restarting an existing container alone does not apply changed Compose environment variables. Preserve existing service passwords and storage. See upgrade preparation. [S1][S10]

2. Match claims to the verifier

The examples below issue a token for one authorized room and calculate its lifetime at signing time. Do not copy a fixed exp timestamp from a decoded sample. JWT NumericDate values use seconds, not JavaScript milliseconds. [S6][S7][S8]

Field Value in this guide What to verify
Header alg HS256 Matches the configured signature algorithm and shared secret
iss my_app Accepted issuer list
aud my_app Accepted audience list
sub Main XMPP domain With domain verification enabled, matches the target domain
room One authorized literal room Matches the actual meeting; no wildcard grant
iat Current Unix seconds Issuer clock is correct
exp Current time plus 600 seconds Future when verified; refresh only after another authorization check
context.user.id Server-authenticated user ID Derived from the trusted login session

These values describe the explicit configuration above, not every supported Jitsi token format. The reviewed verifier requires sub and normally a room claim, validates the signed token and checks the room at MUC admission. Literal room claims are lowercased in the reviewed source; the examples choose a consistent lowercase naming convention. [S2][S3][S6][S7][S8]

Docker domain versus public hostname

Docker’s main XMPP_DOMAIN defaults to meet.jitsi, which may differ from the browser hostname meet.example.com. With JWT_ENABLE_DOMAIN_VERIFICATION=1, use that actual main XMPP domain as sub for this single-domain setup. The template’s default is verification off; this guide deliberately turns it on. Disabling the check to hide a mismatch changes the policy. [S2][S3]

Set these backend-only variables in your server environment or secret manager. JWT_SUB is an input to our examples, not a docker-jitsi-meet setting: [S2][S6][S7]

Config
JWT_APP_ID=my_app
JWT_APP_SECRET=THE_SAME_PRIVATE_64_HEX_CHARACTER_SECRET
JWT_SUB=meet.jitsi

Replace JWT_SUB if your main domain differs. Do not use * for the subject or room in an ordinary per-meeting issuer. The reviewed verifier supports wildcard access, which is broader than the policy these examples implement. [S3]

3. Authorize first, then sign on the backend

Both functions require lookupMeetingGrant(userId, meetingId), implemented by your application against trusted membership or booking data. It must return null/None for denied access, or an object with canJoin equal to the boolean true and the canonical room name. The user ID must come from a verified login session, not the request body. These are signing helpers, not ready-to-expose HTTP endpoints. [S3][S6][S7]

The room pattern is an application convention for these examples, not Jitsi’s universal naming limit. The helpers refuse wildcard rooms and do not copy arbitrary user-provided claims or moderator flags. Authorize role grants separately if your deployment uses them. [S3][S4]

Node.js

Install jsonwebtoken in your backend project. The example was checked with Node 22.23.4 and jsonwebtoken 9.0.3. Save as jitsi-token.mjs in an ES-module backend. [S6]

Terminal
npm install jsonwebtoken
JavaScript
import jwt from "jsonwebtoken";

export async function issueJitsiToken(userId, meetingId, lookupMeetingGrant) {
  if (typeof userId !== "string" || !userId ||
      typeof meetingId !== "string" || !meetingId) {
    throw new Error("Authentication and meeting ID required");
  }
  const grant = await lookupMeetingGrant(userId, meetingId);
  if (grant?.canJoin !== true) throw new Error("Meeting access denied");
  if (typeof grant.room !== "string" ||
      !/^[a-z0-9][a-z0-9-]{0,63}$/.test(grant.room)) {
    throw new Error("Invalid authorized room");
  }
  const appId = process.env.JWT_APP_ID;
  const secret = process.env.JWT_APP_SECRET;
  const subject = process.env.JWT_SUB;
  if (!appId || !/^[0-9a-f]{64}$/.test(secret ?? "") ||
      !subject || subject === "*") {
    throw new Error("JWT server configuration missing or invalid");
  }
  return jwt.sign(
    { room: grant.room, context: { user: { id: userId } } },
    secret,
    { algorithm: "HS256", issuer: appId, audience: appId,
      subject, expiresIn: 600 },
  );
}

jsonwebtoken supplies iat and calculates exp from the numeric lifetime in seconds. Pass the actual authenticated user and an authorization lookup; do not replace the lookup with an unconditional allow response. [S6]

Python

Install PyJWT in your backend environment. This example was checked with PyJWT 2.15.1. Save as jitsi_token.py; the lookup in this version is synchronous. [S7]

Terminal
python -m pip install PyJWT
Python
import os
import re
import time
import jwt  # PyJWT


def issue_jitsi_token(user_id, meeting_id, lookup_meeting_grant):
    if not isinstance(user_id, str) or not user_id or \
            not isinstance(meeting_id, str) or not meeting_id:
        raise PermissionError("Authentication and meeting ID required")
    grant = lookup_meeting_grant(user_id, meeting_id)
    if not isinstance(grant, dict) or grant.get("canJoin") is not True:
        raise PermissionError("Meeting access denied")
    room = grant.get("room")
    if not isinstance(room, str) or not re.fullmatch(r"[a-z0-9][a-z0-9-]{0,63}", room):
        raise ValueError("Invalid authorized room")
    app_id = os.environ.get("JWT_APP_ID")
    secret = os.environ.get("JWT_APP_SECRET", "")
    subject = os.environ.get("JWT_SUB")
    if not app_id or not re.fullmatch(r"[0-9a-f]{64}", secret) or \
            not subject or subject == "*":
        raise ValueError("JWT server configuration missing or invalid")
    now = int(time.time())
    payload = {
        "iss": app_id, "aud": app_id, "sub": subject,
        "room": room, "iat": now, "exp": now + 600,
        "context": {"user": {"id": user_id}},
    }
    return jwt.encode(payload, secret, algorithm="HS256")

Keep credentials out of frontend bundles. Your HTTP handler still needs authenticated sessions, application authorization, appropriate request protection and rate limits. Return tokens over HTTPS with a policy that avoids caching and logging credentials. Treat issued tokens as sensitive bearer credentials; payloads are not encrypted merely because they are signed. [S6][S7][S8]

4. Pass only the finished token to the client

For an existing application using the IFrame API, supply the token received from your authenticated backend as the jwt option and use the same authorized room name as the token. The API also needs the server’s external_api.js; follow the complete embedding guide for loading, lifecycle and CSP details. [S9]

JavaScript
const api = new JitsiMeetExternalAPI("meet.example.com", {
  roomName: authorizedRoomFromServer,
  jwt: tokenFromAuthenticatedBackend,
  parentNode: document.getElementById("meet"),
});

A ?jwt=... meeting URL also carries a credential. Avoid sharing it publicly or placing it in analytics and support logs. Passing a token through an iframe option does not make it invisible to the person using the browser. Do not send the signing secret to the client. [S8][S9]

5. Verify guests and moderator permissions separately

Tokenless access

ENABLE_GUESTS=1 adds an anonymous guest route when authentication is enabled. JWT_ALLOW_EMPTY=1 instead allows an empty token on the token-authenticated host. They are different mechanisms, and neither should be enabled merely to make a failed JWT test pass. This guide leaves both disabled. [S2][S3]

If you intentionally allow guests, test tokenless entry before a host arrives, after the host arrives and with the lobby enabled. Host waiting and admission depend on the active modules and conference policy; the guest flag alone is not proof that every desired restriction is enforced. [S2][S5]

Role claims

The default Docker token-verification module is not proof that a particular token-affiliation policy is active. Inspect the installed module and the generated conference component’s modules_enabled. The pinned token_affiliation implementation maps context.user.moderator: true to owner; it also recognizes owner-like affiliation strings. A false moderator value does not override an owner affiliation. Other automatic owner policies can affect the result. [S2][S4][S5]

Our signing helpers omit role claims because they do not configure a role policy. If you add role claims, derive them from trusted backend permissions and follow the moderator diagnostic checklist. Verify both admission and moderator actions using separate host and participant sessions, including the participant arriving first and the host leaving and rejoining. Do not turn a reported module configuration into a universal fix. [S4][S5]

6. Test allowed and denied cases

Local signing-library tests are useful, but a library accepting a token is not proof that your Jitsi server accepts or scopes it correctly. Complete these deployment acceptance checks on a non-production setup: [S2][S3][S4][S5]

Case Intended result for this guide’s policy
Signed-in user lacks meeting permission Backend refuses to issue a token
Valid authorized room token User enters the matching meeting
Same token used for another room Admission denied
Signature altered or wrong secret Token rejected
Wrong issuer or audience Token rejected
Wrong subject with domain verification enabled Room admission denied
Expired token on a new connection Token rejected
No token Anonymous admission denied
Participant attempts moderator-only action Denied under the chosen role policy

Check server and issuer clocks when testing expiry. Do not assume a 10-minute token ends an already established call after 10 minutes; test rejoin and reconnect and implement meeting-duration policy separately. For a long meeting, a refresh endpoint must repeat authorization before issuing a new token. [S3][S6][S7][S8]

When diagnosing, inspect only the necessary sanitized fields and module names. Do not paste real tokens into online decoders or enable verbose production logging without reviewing what it exposes. See JWT failure diagnostics for existing failures. [S3][S8]

Debian and Ubuntu package differences

Docker .env flags do not configure package installations. Upstream documents jitsi-meet-tokens, token authentication in the main Prosody virtual host, and token_verification on the conference component. Package paths include /etc/prosody/conf.avail/meet.example.com.cfg.lua and /etc/jitsi/meet/meet.example.com-config.js; replace the example hostname. [S12]

Use the actual configured main XMPP domain for sub. The reviewed token utility defaults domain verification to true when no overriding setting is supplied; Docker’s template supplies its own default. Installed package versions and administrator overrides can differ, so inspect the active configuration. Do not transplant Docker Jicofo or guest settings into a package server: current upstream package guidance describes a different authentication arrangement. [S2][S3][S12]

Open questions and maintenance

We exercised the signing helpers with generated temporary secrets, tested denied access and invalid tokens, and checked Docker configuration propagation. No live meeting, Prosody room-admission, tokenless browser, role, reconnect or package-install test was completed for this revision. Both issuer implementations require your application’s real authorization lookup.

Recheck after token-library, Docker-template or role-module changes. Keep server-side access checks and secret rotation in the application’s maintenance plan. The next content review is the dedicated JWT troubleshooting page, including its diagnostic token examples.

Sources

Reviewed 24 October 2026; source snapshots describe code, not the contents of every installed image.

Need help integrating an issuer with your application? See Jitsi integration support.

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 guide work for you?

Something on this page is outdated or wrong?

Frequently asked questions

What claims does a Jitsi JWT need?

For this guide's HS256 setup, use the configured app ID for iss and aud, the configured main XMPP domain for sub, one authorized room name for room, and a future exp in Unix seconds. The examples calculate iat and exp at signing time. Match these values to the actual server settings.

Can guests join without a token?

An anonymous guest domain and an empty-token allowance are separate settings. This guide starts with ENABLE_GUESTS=0 and JWT_ALLOW_EMPTY=0. Enabling guests changes the access policy, so test tokenless admission, host arrival and lobby behavior separately.

Does moderator false guarantee that a user cannot moderate?

No. The active affiliation module and other owner policies decide roles. A verified token alone does not prove role enforcement. Check the installed module, supported claims and automatic owner settings, then test host and participant roles in separate sessions.

Does token expiry automatically end a running meeting?

Do not use JWT expiry as a guaranteed meeting-duration timer. Expiry is checked when the token is verified. Test reconnect and rejoin behavior on your deployment, and use a separate meeting-lifecycle policy if a call must end at a particular time.

Can I sign Jitsi tokens in the browser?

Keep the signing secret on your backend. Authenticate the user and check meeting authorization before issuing a token. Send only the finished token to the client, avoid logging it and do not paste real tokens into public decoder websites.

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