[GH-ISSUE #5399] JWT group sync with embedded Dex + Zitadel IdP does not work without manual config changes #11167

Closed
opened 2026-08-05 01:28:46 -04:00 by saavagebueno · 1 comment
Owner

Originally created by @aliencord on GitHub (Feb 19, 2026).
Original GitHub issue: https://github.com/netbirdio/netbird/issues/5399

Description

When using the combined server with embedded Dex and adding Zitadel as an identity provider through the dashboard UI, enabling JWT group sync does not work out of the box. Two manual configuration changes are required that the UI/server should handle automatically.

Environment

  • NetBird Server: 0.65.3 (combined/embedded Dex mode)
  • Dashboard: v2.32.4
  • Identity Provider: Zitadel (self-hosted), added via dashboard Settings → Identity Providers with type "Zitadel"

Steps to Reproduce

  1. Deploy combined NetBird server with embedded Dex
  2. Add Zitadel as an IdP through the dashboard UI (Settings → Identity Providers → Zitadel)
  3. In Zitadel, create a groupsClaim action on the Complement Token flow (Pre Userinfo creation + Pre access token creation) and assign project roles to the user
  4. In NetBird dashboard, enable JWT group sync with claim name groups
  5. Log out, log back in via Zitadel
  6. Expected: User's Zitadel roles appear as groups in NetBird
  7. Actual: No groups are synced. If "allowed groups" is set, all users get 401.

Root Cause

Two issues in the chain prevent groups from flowing through:

Issue 1: Dashboard does not request groups scope from Dex

The dashboard's AUTH_SUPPORTED_SCOPES defaults to openid profile email offline_access. When JWT group sync is enabled in the UI, the groups scope is not automatically added. Without this scope, Dex does not include the groups claim in the ID token it issues to the dashboard, even though Dex has the groups internally.

Evidence: The Dex refresh_token table stores claims_groups: ["Hyplex_Developer","Test"] but the token scopes are ["openid","profile","email","offline_access"] — missing groups.

Workaround: Manually add groups to the AUTH_SUPPORTED_SCOPES environment variable on the dashboard container.

Issue 2: Zitadel Dex connector missing required scope

When adding Zitadel as an IdP through the UI, the OIDC connector is created with scopes ["openid","profile","email"]. Zitadel requires the urn:zitadel:iam:org:project:roles scope to include project roles in the userinfo response (which is what Dex fetches when getUserInfo: true).

Workaround: Manually update the connector config in idp.db to add urn:zitadel:iam:org:project:roles to the scopes array.

Suggested Fix

  1. When JWT group sync is enabled via the API/UI, automatically ensure the dashboard client requests the groups scope from Dex (or document this requirement prominently in the UI).
  2. When the IdP type is "Zitadel", automatically include urn:zitadel:iam:org:project:roles in the Dex OIDC connector scopes.

Additional Context

  • The insecureEnableGroups: true and getUserInfo: true flags ARE correctly set on the connector — so the intent is clearly there, just the scopes are missing.
  • Setting "allowed groups" in JWT group sync without the above fixes causes a complete lockout (401 on all API requests for all users, including local Dex users), requiring direct SQLite database intervention to recover.
Originally created by @aliencord on GitHub (Feb 19, 2026). Original GitHub issue: https://github.com/netbirdio/netbird/issues/5399 ## Description When using the combined server with embedded Dex and adding Zitadel as an identity provider through the dashboard UI, enabling JWT group sync does not work out of the box. Two manual configuration changes are required that the UI/server should handle automatically. ## Environment - NetBird Server: `0.65.3` (combined/embedded Dex mode) - Dashboard: `v2.32.4` - Identity Provider: Zitadel (self-hosted), added via dashboard Settings → Identity Providers with type "Zitadel" ## Steps to Reproduce 1. Deploy combined NetBird server with embedded Dex 2. Add Zitadel as an IdP through the dashboard UI (Settings → Identity Providers → Zitadel) 3. In Zitadel, create a `groupsClaim` action on the Complement Token flow (Pre Userinfo creation + Pre access token creation) and assign project roles to the user 4. In NetBird dashboard, enable JWT group sync with claim name `groups` 5. Log out, log back in via Zitadel 6. **Expected**: User's Zitadel roles appear as groups in NetBird 7. **Actual**: No groups are synced. If "allowed groups" is set, all users get 401. ## Root Cause Two issues in the chain prevent groups from flowing through: ### Issue 1: Dashboard does not request `groups` scope from Dex The dashboard's `AUTH_SUPPORTED_SCOPES` defaults to `openid profile email offline_access`. When JWT group sync is enabled in the UI, the `groups` scope is not automatically added. Without this scope, Dex does not include the `groups` claim in the ID token it issues to the dashboard, even though Dex has the groups internally. **Evidence**: The Dex `refresh_token` table stores `claims_groups: ["Hyplex_Developer","Test"]` but the token scopes are `["openid","profile","email","offline_access"]` — missing `groups`. **Workaround**: Manually add `groups` to the `AUTH_SUPPORTED_SCOPES` environment variable on the dashboard container. ### Issue 2: Zitadel Dex connector missing required scope When adding Zitadel as an IdP through the UI, the OIDC connector is created with scopes `["openid","profile","email"]`. Zitadel requires the `urn:zitadel:iam:org:project:roles` scope to include project roles in the userinfo response (which is what Dex fetches when `getUserInfo: true`). **Workaround**: Manually update the connector config in `idp.db` to add `urn:zitadel:iam:org:project:roles` to the scopes array. ## Suggested Fix 1. When JWT group sync is enabled via the API/UI, automatically ensure the dashboard client requests the `groups` scope from Dex (or document this requirement prominently in the UI). 2. When the IdP type is "Zitadel", automatically include `urn:zitadel:iam:org:project:roles` in the Dex OIDC connector scopes. ## Additional Context - The `insecureEnableGroups: true` and `getUserInfo: true` flags ARE correctly set on the connector — so the intent is clearly there, just the scopes are missing. - Setting "allowed groups" in JWT group sync without the above fixes causes a complete lockout (401 on all API requests for all users, including local Dex users), requiring direct SQLite database intervention to recover.
Author
Owner

@aliencord commented on GitHub (Feb 19, 2026):

note that i had Claude make an issue from my notepad notes after upgrading to the latest version of netbird. excuse my em dashes.

<!-- gh-comment-id:3930545404 --> @aliencord commented on GitHub (Feb 19, 2026): note that i had Claude make an issue from my notepad notes after upgrading to the latest version of netbird. excuse my em dashes.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#11167