# How do I add Keycloak or Entra ID single sign-on to Jitsi Meet?

> Jitsi only trusts JWTs that Prosody can verify, signed with your app secret or a key it can fetch, and it reads them from the ?jwt= URL parameter, so an OIDC login needs a bridge. The practical bridge is an adapter such as jitsi-contrib/jitsi-oidc-adapter: Jitsi sends users to it through tokenAuthUrl, it runs the OIDC code flow with Keycloak or Entra ID, then mints a room-scoped HS256 Jitsi token. On Docker set ENABLE_AUTH=1, AUTH_TYPE=jwt, JWT_APP_ID, JWT_APP_SECRET, ENABLE_GUESTS=1 and TOKEN_AUTH_URL. With Docker defaults every signed-in user becomes a moderator and guests wait for a host.

Source: https://jitsi.help/guides/jitsi-keycloak-oidc-sso/
Updated: October 5, 2026
Publisher: Jitsi Help (https://jitsi.help/)

## Who this is for

You run Jitsi Meet and want people to sign in with your company identity provider (Keycloak, Microsoft Entra ID, Authentik or any OpenID Connect provider) before they can start a meeting. Guests should still be able to join once a host is there. You want this to work in the browser and in the Jitsi mobile apps.

## How it works

**Jitsi only understands its own JWT.** Prosody's token plugin checks a JWT when the browser connects. The token is passed as the `token` parameter of the BOSH or WebSocket URL, and Jitsi Meet takes it from the page URL as `?jwt=`. Prosody verifies the signature with `app_secret` (HS256), or with a public key from `asap_key_server` or `cache_keys_url` (RS256, needs a `kid` header). It then checks `iss`, `aud`, `room`, `sub` and `exp`.

**Why an OIDC token does not just work.** An identity provider's `id_token` is signed with the provider's keys, not your app secret. It has no `room` claim, and it comes back in the URL fragment (`#id_token=`) or a form POST, which Jitsi does not read. Native OIDC redirect support was requested in jitsi-meet issue #16576. A maintainer said in January 2026 that an intermediate step would still be needed. The issue was closed as stale on 2026-03-29.

**Bridge patterns.** Issue #16576 lists three:

1. **Adapter or token service (recommended).** A small service runs the OIDC login, then signs a Jitsi JWT with your app secret. `jitsi-contrib/jitsi-oidc-adapter` is the maintained community option. The older `nordeck/jitsi-keycloak-adapter` README now tells users to switch to it.
2. **Prosody verifies the provider's token directly.** Since PR #16649 (2.0.10655), `cache_keys_url` accepts a JWKS URL. You still need a page that copies the fragment token into `?jwt=`, the implicit flow, and `asap_require_room_claim = false`.
3. **Pre-signed links** from your own app. These suit portals and bots, not interactive sign-in.

A reverse proxy login (for example in front of the web page) is not enough on its own. Prosody still needs a JWT on the XMPP connection.

**The login redirect.** When a user who is not signed in needs to log in, Jitsi Meet sends them to `tokenAuthUrl`. It fills in `{room}` and `{state}`; state holds the room, tenant, config overrides and `ios=true`, `android=true` or `electron=true`. Without `tokenAuthUrl`, Jitsi shows the username and password dialog instead. The mobile apps open `tokenAuthUrl` in the system browser and wait for a deep link that carries the JWT back. The adapter returns `org.jitsi.meet://` links for iOS, an Android `intent://` link, and `jitsi-meet://` for the Electron app.

**Who becomes moderator.** In Docker, `JICOFO_ENABLE_AUTH` defaults to `ENABLE_AUTH`, and the Jicofo auth type follows `AUTH_TYPE`. So with `AUTH_TYPE=jwt`, Jicofo authentication is on. Jicofo then grants owner (moderator) to every authenticated member. Guests join on the anonymous domain and are not authenticated. To decide moderators from a token claim, the bundled `mod_token_affiliation` reads `context.user.moderator` (`true` or `"true"`) or `context.user.affiliation` (`owner`, `moderator`, `teacher`).

## Before you start

- Jitsi with HTTPS at `https://meet.example.com`: docker-jitsi-meet stable-11248 or packages 2.0.11248.
- An OIDC provider reachable by users and by the adapter. Keycloak 26.8.0 is current as of 2026-10-01.
- A random app secret shared by the adapter and Prosody, for example `openssl rand -hex 32`.
- For Keycloak: a realm (here `example`) and admin access. The issuer is `https://auth.example.com/realms/example`.
- For Entra ID: an app registration. The issuer is `https://login.microsoftonline.com/<tenant-id>/v2.0`.

## Steps

### 1. Create the OIDC client

**Keycloak** (adapter docs):
- Create a client, for example `jitsi`.
- Valid redirect URIs: `https://meet.example.com/oidc/tokenize`
- Web origins: `https://meet.example.com`
- Either turn off Client authentication (public client), or leave it on and copy the client secret.

**Entra ID:** register a web app with redirect URI `https://meet.example.com/oidc/tokenize` and create a client secret. The adapter reads endpoints from the issuer's discovery document. Entra's discovery document lists a userinfo endpoint. This path is untested (see Open questions).

### 2. Docker (docker-jitsi-meet stable-11248)

**Add the adapter** as a service in `docker-compose.override.yml`. Compose merges that file automatically:

```yaml
services:
  oidc-adapter:
    image: ghcr.io/jitsi-contrib/jitsi-oidc-adapter
    restart: unless-stopped
    environment:
      - OIDC_ISSUER_URL=https://auth.example.com/realms/example
      - OIDC_CLIENT_ID=jitsi
      - OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET}
      - JWT_APP_ID=${JWT_APP_ID}
      - JWT_APP_SECRET=${JWT_APP_SECRET}
      - JWT_EXP_SECOND=3600
      - AUTO_RETURN_TO_APP=true
    networks:
      meet.jitsi:
  web:
    depends_on:
      - oidc-adapter
```

The variable names and the port (9000) come from the adapter image. The adapter's own Docker guide publishes port 9000 on the host instead and is marked "NOT READY YET".

**Proxy `/oidc/` through Jitsi's web container.** Create `~/.jitsi-meet-cfg/web/nginx-custom/oidc.conf`. The stable-11248 web image copies `/config` into `/run/web/config` and includes `nginx-custom/*.conf`:

```nginx
location ~ /oidc/ {
    proxy_pass http://oidc-adapter:9000;
    proxy_http_version 1.1;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Host $http_host;
}
```

Keep the `Host` header: the adapter builds `https://<host>/oidc/tokenize` from it.

**Set `.env`**:

```ini
ENABLE_AUTH=1
AUTH_TYPE=jwt
JWT_APP_ID=meet_example
JWT_APP_SECRET=REPLACE_WITH_RANDOM_SECRET
OIDC_CLIENT_SECRET=REPLACE_WITH_CLIENT_SECRET
ENABLE_GUESTS=1
TOKEN_AUTH_URL=https://meet.example.com/oidc/auth?state={state}
XMPP_MODULES=persistent_lobby
XMPP_MUC_MODULES=muc_wait_for_host
```

`TOKEN_AUTH_URL` becomes `config.tokenAuthUrl`. `ENABLE_GUESTS` adds the `anonymousdomain`, so guests can wait for a host.

**Start it:**

```bash
docker compose up -d
```

### 3. Debian/Ubuntu packages (2.0.11248)

1. Install token support. It asks for the app ID and secret and sets `authentication = "token"`:

   ```bash
   sudo apt-get install jitsi-meet-tokens
   ```

2. Add the guest VirtualHost to the Prosody config, and `anonymousdomain` to `/etc/jitsi/meet/meet.example.com-config.js`:

   ```lua
   VirtualHost "guest.meet.example.com"
       authentication = "jitsi-anonymous"
       c2s_require_encryption = false
   ```

   ```js
   anonymousdomain: 'guest.meet.example.com',
   ```

3. Add `persistent_lobby` to the main VirtualHost `modules_enabled`, and `muc_wait_for_host` to the `conference` component.
4. Install the adapter as a systemd service. Proxy `/oidc/` to `http://127.0.0.1:9000` with the adapter's `oidc.conf` in `/etc/jitsi/meet/jaas/`.
5. Set the login URL in `/etc/jitsi/meet/meet.example.com-config.js`:

   ```js
   config.tokenAuthUrl = 'https://meet.example.com/oidc/auth?state={state}';
   ```

6. Restart:

   ```bash
   sudo systemctl restart prosody jicofo jitsi-videobridge2 nginx
   ```

### 4. Choose how moderators are decided

- **Every signed-in user is a moderator (Docker default).** Leave Jicofo auth on.
- **Only some users are moderators.** Add `moderator: true` under `context.user` for chosen users. The adapter builds `context.user` in `src/context.ts`. Then load `token_affiliation` and stop Jicofo from promoting everyone. In Docker that means `XMPP_MUC_MODULES=muc_wait_for_host,token_affiliation`, `JICOFO_ENABLE_AUTH=0`, `ENABLE_AUTO_OWNER=0` and `WAIT_FOR_HOST_DISABLE_AUTO_OWNERS=1`. We derived this from source, so test it first.

### 5. Direct JWKS verification (alternative, packages)

Packages 2.0.10655 and newer can verify Keycloak tokens without an adapter. A maintainer gave this Prosody setup in #16576:

```lua
asap_accepted_issuers = { "https://auth.example.com/realms/example" }
asap_accepted_audiences = { "jitsi" }
asap_require_room_claim = false;
VirtualHost "meet.example.com"
    authentication = "token"
    cache_keys_url = "https://auth.example.com/realms/example/protocol/openid-connect/certs";
```

You also need a `plugin.head.html` script that turns `#id_token=` into `?jwt=`, plus the implicit flow. Keycloak warns that implicit-flow tokens are "very hard to invalidate". docker-jitsi-meet has no `cache_keys_url` variable.

## Configuration reference

| Name | Where | Default | What it does |
|---|---|---|---|
| `ENABLE_AUTH` | Docker `.env` | `0` | Turns on authentication |
| `AUTH_TYPE` | Docker `.env` | `internal` | `jwt` selects token auth |
| `JWT_APP_ID` | Docker `.env` | unset | Prosody `app_id`. Default accepted issuer |
| `JWT_APP_SECRET` | Docker `.env` | unset | Prosody `app_secret`, HS256 key |
| `JWT_ACCEPTED_ISSUERS` / `JWT_ACCEPTED_AUDIENCES` | Docker `.env` | issuers: app ID; audiences: `*` | Allowed `iss` and `aud` |
| `JWT_ENABLE_DOMAIN_VERIFICATION` | Docker `.env` | `false` | When on, `sub` must equal `XMPP_DOMAIN` (default `meet.jitsi`) or `*` |
| `JWT_ALLOW_EMPTY` | Docker `.env` | `0` | Lets users connect without a token |
| `ENABLE_GUESTS` | Docker `.env` | `0` | Adds the anonymous guest domain |
| `TOKEN_AUTH_URL` | Docker `.env` | unset | Sets `config.tokenAuthUrl` |
| `JICOFO_ENABLE_AUTH` | Docker `.env` | value of `ENABLE_AUTH` | Jicofo auth. When on, every authenticated user is moderator |
| `ENABLE_AUTO_OWNER` | Docker `.env` | Jicofo default | First member becomes moderator when Jicofo auth is off |
| `JICOFO_AUTH_LIFETIME` | Docker `.env` | `24 hours` | Jicofo auth session lifetime |
| `XMPP_MODULES` / `XMPP_MUC_MODULES` | Docker `.env` | unset | Extra Prosody modules |
| `tokenAuthUrl` | `config.js` | unset | Login service URL with `{room}`, `{state}`, `{code_challenge}` |
| `tokenLogoutUrl` | `config.js` | unset | Logout service URL |
| `cache_keys_url` | Prosody VirtualHost | unset | JWKS URL for RS256 tokens, 2.0.10655+ |
| `asap_require_room_claim` | Prosody | `true` | Requires the `room` claim |
| `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_SCOPES` | adapter env | client `jitsi`, scopes `openid profile email` | Provider settings |
| `JWT_EXP_SECOND` | adapter env | `10800` | Lifetime of minted Jitsi tokens |
| `AUTO_RETURN_TO_APP` | adapter env | `false` | Redirect mobile and desktop apps back automatically |

**Claims the adapter puts in its token**: `aud` and `iss` = `JWT_APP_ID`; `sub` = the tenant or meeting host; `room` = the requested room; `iat`, `nbf`, `exp`; `context.user` with `id` (the OIDC `sub`), `name`, `email`, `lobby_bypass` and `security_bypass`.

## Common mistakes

- **Pasting the provider's token as `?jwt=`** while Prosody uses `app_secret`. Prosody logs `Error verifying token err:not-allowed, reason:Invalid signature`.
- **Putting the token in the fragment.** Jitsi parses hash values as JSON, so `#jwt=` needs the value in quotes. `?jwt=` does not.
- **Setting `tokenAuthUrlAutoRedirect`.** The adapter docs set it, but it is not in jitsi-meet's config type on master as of 2026-10-05.
- **Everyone is a moderator.** That is Jicofo auth with the Docker defaults. See step 4.
- **`'kid' claim is missing`** after adding `cache_keys_url`. With a key URL set, Prosody verifies only RS-signed tokens with `kid`, so HS256 tokens from other tools fail.
- **`Room does not match the room from token`.** The adapter scopes each token to one room, so the same link cannot open another room.
- **Wrong `sub` with domain verification on.** On Docker, `sub` must be `meet.jitsi` (`XMPP_DOMAIN`) or `*`. With the Docker default (off), `sub` is only required to exist. On packages, the domain is your public hostname.
- **Redirect URI mismatch** in Keycloak. It must be exactly `https://meet.example.com/oidc/tokenize`.
- **Proxy drops the `Host` header,** so the adapter builds a wrong redirect URI.

## Verify

1. Adapter health. The adapter answers on `/oidc/health`:

   ```bash
   curl -s https://meet.example.com/oidc/health
   ```

   Expect `healthy`.

2. The adapter found the provider. At startup, `docker compose logs oidc-adapter` shows `AUTH_ENDPOINT:`, `TOKEN_ENDPOINT:` and `USERINFO_ENDPOINT:` lines.
3. Open `https://meet.example.com/testroom` in a private window. Click the log-in button on the wait-for-host screen. You should go to Keycloak, then back to `/testroom?jwt=...`.
4. Decode the token's middle part and confirm `room` is `testroom`, `aud` and `iss` equal `JWT_APP_ID`, and `exp` is about one hour ahead.
5. In a second browser, open the same room without signing in. The guest should wait until the host is in, then join without moderator rights.

## If it still fails

- Prosody: `docker compose logs prosody | grep -i token`. Look for `Error verifying token` with a reason such as `Invalid signature`, `room claim is missing` or `token required`.
- Jicofo: `docker compose logs jicofo | grep -i ownership` shows `Granting ownership to` for each promoted user.
- Adapter: `docker compose logs oidc-adapter`. Discovery or userinfo errors point at `OIDC_ISSUER_URL` or the client secret.
- Mobile app does not return: test with Firefox on Android. The adapter docs report a "broken tab" problem with Chromium browsers when `AUTO_RETURN_TO_APP=true`.

## What we have not confirmed yet

We checked everything above against the Jitsi token library, Jicofo, the docker-jitsi-meet templates, the adapter source and maintainer comments on 2026-10-05. We have not yet run the full Keycloak or Entra ID flow on stable-11248. These points are still open:


- The adapter's Docker guide says "NOT READY YET" and was tested with stable-10741. The standalone guide was tested on Debian 12 with 2.0.10741. Neither is confirmed on 11248.
- The compose override (adapter on the `meet.jitsi` network, proxy to `oidc-adapter:9000`) is our adaptation of the adapter docs. Untested.
- Claim-based moderators: the combination `JICOFO_ENABLE_AUTH=0`, `ENABLE_AUTO_OWNER=0`, `WAIT_FOR_HOST_DISABLE_AUTO_OWNERS=1` and `token_affiliation` is derived from source and needs a real test. Includes whether the wait-for-host login still triggers with Jicofo auth off.
- Nothing in jitsi-meet's Prosody plugins reads the adapter's `lobby_bypass` and `security_bypass` flags (code search, 2026-10-05). Their effect is unconfirmed.
- Entra ID with the adapter: discovery and userinfo should work, but untested. Entra's multi-tenant discovery uses `{tenantid}` in the issuer, so use the tenant-specific issuer URL.
- Direct JWKS on Docker: `XMPP_CONFIGURATION` lines are rendered into the main VirtualHost, so `cache_keys_url` could be added that way. Untested.
- When `tokenAuthUrlAutoRedirect` was removed from jitsi-meet, and whether it had any replacement.
- White-label mobile apps use their own URL scheme. The adapter hardcodes `org.jitsi.meet` and `jitsi-meet`.
- Tokens travel in the page URL (`?jwt=`), so they can appear in browser history. Whether the default nginx access log records them was not checked.

## Sources

- [Jitsi handbook, Token Authentication](https://jitsi.github.io/handbook/docs/devops-guide/token-authentication), checked 2026-10-05, official doc
- [lib-jitsi-meet doc/tokens.md, JWT authentication Prosody plugin](https://github.com/jitsi/lib-jitsi-meet/blob/master/doc/tokens.md), checked 2026-10-05, official doc
- [Jitsi handbook, Self-Hosting Guide: Docker (Authentication, Authentication using JWT tokens)](https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-docker), source last changed 2026-10-02, official doc
- [jitsi-meet issue #16576 Native OpenID Connect (OIDC) redirect support](https://github.com/jitsi/jitsi-meet/issues/16576), 2025-10-22 to 2026-03-29 (closed as stale), maintainer comments (damencho, aaronkvanmeerten) and community reports
- [jitsi-meet PR #16649 feat(jwt): Supports JWKS endpoint](https://github.com/jitsi/jitsi-meet/pull/16649), merged 2025-11-17, first in 2.0.10655, maintainer PR
- [jitsi-meet resources/prosody-plugins/token/util.lib.lua](https://github.com/jitsi/jitsi-meet/blob/master/resources/prosody-plugins/token/util.lib.lua), checked 2026-10-05, source code
- [docker-jitsi-meet prosody/rootfs/defaults/conf.d/jitsi-meet.cfg.lua at stable-11248](https://github.com/jitsi/docker-jitsi-meet/blob/stable-11248/prosody/rootfs/defaults/conf.d/jitsi-meet.cfg.lua), source code
- [docker-jitsi-meet jicofo/rootfs/defaults/jicofo.conf at stable-11248](https://github.com/jitsi/docker-jitsi-meet/blob/stable-11248/jicofo/rootfs/defaults/jicofo.conf), source code
- [jicofo ChatRoomRoleManager.kt (AuthenticationRoleManager, AutoOwnerRoleManager) and JitsiMeetConferenceImpl.java](https://github.com/jitsi/jicofo/blob/master/jicofo/src/main/kotlin/org/jitsi/jicofo/xmpp/muc/ChatRoomRoleManager.kt), checked 2026-10-05, source code
- [jitsi-meet resources/prosody-plugins/mod_token_affiliation.lua](https://github.com/jitsi/jitsi-meet/blob/master/resources/prosody-plugins/mod_token_affiliation.lua), in 2.0.10978 and newer, source code
- [jitsi-meet config.js (tokenAuthUrl, tokenLogoutUrl, tokenAuthInline)](https://github.com/jitsi/jitsi-meet/blob/master/config.js), checked 2026-10-05, source code
- [jitsi-meet react/features/authentication (functions.any.ts, functions.native.ts, actions.native.ts, middleware.any.ts) and base/config/configType.ts](https://github.com/jitsi/jitsi-meet/tree/master/react/features/authentication), checked 2026-10-05, source code
- [jitsi-contrib/jitsi-oidc-adapter README, docs/setup-docker.md and docs/setup-standalone.md](https://github.com/jitsi-contrib/jitsi-oidc-adapter), release v20260328, community project
- [jitsi-contrib/jitsi-oidc-adapter src/adapter.ts, src/config.ts, src/context.ts and Dockerfile](https://github.com/jitsi-contrib/jitsi-oidc-adapter/tree/main/src), v20260328, community source code
- [nordeck/jitsi-keycloak-adapter README](https://github.com/nordeck/jitsi-keycloak-adapter), release v20260623, community project
- [docker-jitsi-meet web templates at stable-11248 (system-config.js, settings-config.js, meet.conf, s6 config script)](https://github.com/jitsi/docker-jitsi-meet/tree/stable-11248/web/rootfs), source code
- [docker-jitsi-meet docker-compose.yml at stable-11248](https://github.com/jitsi/docker-jitsi-meet/blob/stable-11248/docker-compose.yml), 2026-09-14, source code
- [Keycloak, Securing applications: OpenID Connect layers (endpoints, implicit flow)](https://www.keycloak.org/securing-apps/oidc-layers), Keycloak 26.8.0 released 2026-10-01, official doc
- [Microsoft Entra ID token claims reference and v2.0 OpenID configuration](https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference), and https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration, checked 2026-10-05, official doc
- [Docker docs, Merge Compose files](https://docs.docker.com/compose/how-tos/multiple-compose-files/merge/), official doc (Docker)
- [jitsi-meet doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example (muc_mapper_domain_base)](https://github.com/jitsi/jitsi-meet/blob/master/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example), source code

## Need a hand?

Single sign-on touches your identity provider, Prosody and Jicofo at once. Our [integration service](/services/jitsi-integration/) sets it up and tests host, guest and mobile logins, or [contact our engineers](/contact/?topic=troubleshooting) with the Prosody lines that mention `Error verifying token`.

## Frequently asked questions

### Can Jitsi use Keycloak tokens directly, without an adapter?

Partly. Since 2.0.10655 Prosody can check RS256 tokens against a JWKS URL, but you still need a fragment-to-query script and lose room scoping.

### Does this work with Microsoft Entra ID or Azure AD?

The adapter uses standard OIDC discovery and userinfo, which Entra provides. We have not tested it end to end.

### Do guests need an account?

No. With `ENABLE_GUESTS=1` they join on the anonymous domain, and with `muc_wait_for_host` they wait until a signed-in host arrives.

### How do I log users out?

Set `tokenLogoutUrl` to your provider's logout URL. Docker has no variable for it, so put it in `custom-config.js`.

### How do I rotate the app secret?

Change `JWT_APP_SECRET` for both Jitsi and the adapter, then recreate both. Prosody holds one `app_secret`, so tokens signed with the old secret stop working.


---

Jitsi Help is an independent service. It is not affiliated with, endorsed by or sponsored by 8x8, Inc. or the Jitsi project. Jitsi and Jitsi Meet are trademarks of 8x8, Inc., used here only to describe the software we host and support.
