[GH-ISSUE #5380] Bug: Reverse Proxy SSO generates empty client_id in OAuth2 redirect URL when using external IdP (Zitadel) #11342

Open
opened 2026-08-05 01:29:22 -04:00 by saavagebueno · 0 comments
Owner

Originally created by @SuperKali on GitHub (Feb 18, 2026).
Original GitHub issue: https://github.com/netbirdio/netbird/issues/5380

Description

When using the NetBird Reverse Proxy with SSO authentication enabled and an external IdP (Zitadel),
the OAuth2 redirect URL generated by the proxy contains an empty client_id parameter, causing
the authorization request to fail with invalid_request: auth request is missing client_id.

Environment

  • NetBird Management: netbirdio/management:latest
  • NetBird Reverse Proxy: netbirdio/reverse-proxy:latest
  • Identity Provider: Zitadel (self-hosted)
  • Deployment: Docker Compose with Traefik reverse proxy
  • Config format: management.json (legacy stack: separate management + signal + relay)

Steps to Reproduce

  1. Deploy NetBird with external Zitadel IdP using management.json
  2. Configure PKCEAuthorizationFlow in management.json with a valid ClientID:
    "PKCEAuthorizationFlow": {
      "ProviderConfig": {
        "ClientID": "<zitadel-client-id>",
        "AuthorizationEndpoint": "https://your-netbird-domain/oauth/v2/authorize",
        "TokenEndpoint": "https://your-netbird-domain/oauth/v2/token"
      }
    }
    
  3. Configure AuthCallbackURL in HttpConfig:
    "HttpConfig": {
      "AuthCallbackURL": "https://your-netbird-domain/api/reverse-proxy/callback"
    }
    
  4. Enable SSO on a Reverse Proxy service from the dashboard
  5. Visit the proxied service URL in a browser

Expected Behavior

The proxy generates an OAuth2 redirect URL with the correct client_id:

https://your-netbird-domain/oauth/v2/authorize?client_id=<zitadel-client-id>&...

Actual Behavior

The proxy generates an OAuth2 redirect URL with an empty client_id:

https://your-netbird-domain/oauth/v2/authorize?client_id=&code_challenge=...

The full 401 response body from the proxy:

{
  "methods": {
    "oidc": "https://your-netbird-domain/oauth/v2/authorize?client_id=\u0026code_challenge=...&redirect_uri=https%3A%2F%2Fyour-netbird-domain%2Fapi%2Freverse-proxy%2Fcallback&response_type=code&scope=openid+profile+email&state=..."
  }
}

Investigation

  • management.json is correctly mounted and read by the management container
  • PKCEAuthorizationFlow.ProviderConfig.ClientID is correctly set in management.json
  • Management logs confirm OIDC config is loaded successfully at startup
  • Management logs show overriding PKCEAuthorizationFlow.TokenEndpoint and AuthorizationEndpoint
    but never log the ClientID, suggesting it is not being transmitted to the proxy via gRPC
  • The proxy container receives the management gRPC config but the ClientID field appears empty
  • Environment variables NB_PROXY_OIDC_CLIENT_ID, NB_PROXY_OIDC_ENDPOINT, NB_PROXY_OIDC_SCOPES
    are set in the proxy container but are completely ignored by the proxy binary
  • The ReverseProxy section in management.json has no OIDC/ClientID field available

Possible Cause

The management server does not appear to transmit the PKCEAuthorizationFlow.ProviderConfig.ClientID
to the reverse proxy via gRPC when building the SSO redirect URL. The proxy SSO with external IdP
may only work with the new unified netbird-server using config.yaml, not with the legacy
management.json stack.

Workaround

None found. The issue makes Reverse Proxy SSO completely non-functional with external IdP
when using the legacy management.json configuration format.

Questions

  1. Is Reverse Proxy SSO with external IdP supported in the legacy management.json stack?
  2. If yes, which field in management.json or ReverseProxy section should contain the
    ClientID for the proxy SSO flow?
  3. If no, should the documentation explicitly state that migration to config.yaml is required
    for Reverse Proxy SSO with external IdP?
Originally created by @SuperKali on GitHub (Feb 18, 2026). Original GitHub issue: https://github.com/netbirdio/netbird/issues/5380 ### Description When using the NetBird Reverse Proxy with SSO authentication enabled and an external IdP (Zitadel), the OAuth2 redirect URL generated by the proxy contains an empty `client_id` parameter, causing the authorization request to fail with `invalid_request: auth request is missing client_id`. ### Environment - **NetBird Management**: `netbirdio/management:latest` - **NetBird Reverse Proxy**: `netbirdio/reverse-proxy:latest` - **Identity Provider**: Zitadel (self-hosted) - **Deployment**: Docker Compose with Traefik reverse proxy - **Config format**: `management.json` (legacy stack: separate management + signal + relay) ### Steps to Reproduce 1. Deploy NetBird with external Zitadel IdP using `management.json` 2. Configure `PKCEAuthorizationFlow` in `management.json` with a valid `ClientID`: ```json "PKCEAuthorizationFlow": { "ProviderConfig": { "ClientID": "<zitadel-client-id>", "AuthorizationEndpoint": "https://your-netbird-domain/oauth/v2/authorize", "TokenEndpoint": "https://your-netbird-domain/oauth/v2/token" } } ``` 3. Configure `AuthCallbackURL` in `HttpConfig`: ```json "HttpConfig": { "AuthCallbackURL": "https://your-netbird-domain/api/reverse-proxy/callback" } ``` 4. Enable SSO on a Reverse Proxy service from the dashboard 5. Visit the proxied service URL in a browser ### Expected Behavior The proxy generates an OAuth2 redirect URL with the correct `client_id`: ``` https://your-netbird-domain/oauth/v2/authorize?client_id=<zitadel-client-id>&... ``` ### Actual Behavior The proxy generates an OAuth2 redirect URL with an empty `client_id`: ``` https://your-netbird-domain/oauth/v2/authorize?client_id=&code_challenge=... ``` The full 401 response body from the proxy: ```json { "methods": { "oidc": "https://your-netbird-domain/oauth/v2/authorize?client_id=\u0026code_challenge=...&redirect_uri=https%3A%2F%2Fyour-netbird-domain%2Fapi%2Freverse-proxy%2Fcallback&response_type=code&scope=openid+profile+email&state=..." } } ``` ### Investigation - `management.json` is correctly mounted and read by the management container - `PKCEAuthorizationFlow.ProviderConfig.ClientID` is correctly set in `management.json` - Management logs confirm OIDC config is loaded successfully at startup - Management logs show `overriding PKCEAuthorizationFlow.TokenEndpoint` and `AuthorizationEndpoint` but **never log the ClientID**, suggesting it is not being transmitted to the proxy via gRPC - The proxy container receives the management gRPC config but the `ClientID` field appears empty - Environment variables `NB_PROXY_OIDC_CLIENT_ID`, `NB_PROXY_OIDC_ENDPOINT`, `NB_PROXY_OIDC_SCOPES` are set in the proxy container but are completely ignored by the proxy binary - The `ReverseProxy` section in `management.json` has no OIDC/ClientID field available ### Possible Cause The management server does not appear to transmit the `PKCEAuthorizationFlow.ProviderConfig.ClientID` to the reverse proxy via gRPC when building the SSO redirect URL. The proxy SSO with external IdP may only work with the new unified `netbird-server` using `config.yaml`, not with the legacy `management.json` stack. ### Workaround None found. The issue makes Reverse Proxy SSO completely non-functional with external IdP when using the legacy `management.json` configuration format. ### Questions 1. Is Reverse Proxy SSO with external IdP supported in the legacy `management.json` stack? 2. If yes, which field in `management.json` or `ReverseProxy` section should contain the `ClientID` for the proxy SSO flow? 3. If no, should the documentation explicitly state that migration to `config.yaml` is required for Reverse Proxy SSO with external IdP?
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#11342