[GH-ISSUE #5940] "Migration Guide: External IdP to Embedded IdP" old admin account login not working from old Zitadel #12447

Open
opened 2026-08-05 02:05:55 -04:00 by saavagebueno · 6 comments
Owner

Originally created by @axi92 on GitHub (Apr 21, 2026).
Original GitHub issue: https://github.com/netbirdio/netbird/issues/5940

Describe the problem

I installed Netbird self hosted on 6. Nov. 2025 v0.59.12
Now I did the migration Migration Guide: From Coturn to Embedded STUN Server and now I did Migration Guide: External IdP to Embedded IdP with that I migrated to Entra ID. That login works but my user needs to be approved. But I don't have an account left to login and approve my new entraID user...

Is there a way to reset the admin account pw?
Since the initial installation I only had the zitadel account.

To Reproduce

Steps to reproduce the behavior:

  1. Start with v0.59.12
  2. Upgrade over several versions to v0.69.0
  3. Migration Guide: From Coturn to Embedded STUN Server
  4. Migration Guide: External IdP to Embedded IdP
  5. Old Admin account from Zitadel from the setup back in v0.59.12 is not working and I don't have no other account.

Expected behavior

I need an account to login so I can approve my EntraID provider account.

Are you using NetBird Cloud?

Self hosted

NetBird version

to v0.69.0

Is any other VPN software installed?

No

Screenshots

I tried to login with my Entra ID account:
response_message: failed to validate user permissions: user is pending approval

Image

Additional context

Have you tried these troubleshooting steps?

  • Reviewed client troubleshooting (if applicable)
  • Checked for newer NetBird versions
  • Searched for similar issues on GitHub (including closed ones)
  • Restarted the NetBird client
  • Disabled other VPN software
  • Checked firewall settings
Originally created by @axi92 on GitHub (Apr 21, 2026). Original GitHub issue: https://github.com/netbirdio/netbird/issues/5940 **Describe the problem** I installed Netbird self hosted on 6. Nov. 2025 [v0.59.12](https://github.com/netbirdio/netbird/releases/tag/v0.59.12) Now I did the migration [Migration Guide: From Coturn to Embedded STUN Server](https://docs.netbird.io/selfhosted/migration/coturn-to-stun-migration) and now I did [Migration Guide: External IdP to Embedded IdP](https://docs.netbird.io/selfhosted/migration/external-to-embedded-idp) with that I migrated to Entra ID. That login works but my user needs to be approved. But I don't have an account left to login and approve my new entraID user... Is there a way to reset the admin account pw? Since the initial installation I only had the zitadel account. **To Reproduce** Steps to reproduce the behavior: 1. Start with v0.59.12 2. Upgrade over several versions to v0.69.0 3. [Migration Guide: From Coturn to Embedded STUN Server](https://docs.netbird.io/selfhosted/migration/coturn-to-stun-migration) 4. [Migration Guide: External IdP to Embedded IdP](https://docs.netbird.io/selfhosted/migration/external-to-embedded-idp) 5. Old Admin account from Zitadel from the setup back in v0.59.12 is not working and I don't have no other account. **Expected behavior** I need an account to login so I can approve my EntraID provider account. **Are you using NetBird Cloud?** Self hosted **NetBird version** `to v0.69.0` **Is any other VPN software installed?** No **Screenshots** I tried to login with my Entra ID account: `response_message: failed to validate user permissions: user is pending approval` <img width="590" height="373" alt="Image" src="https://github.com/user-attachments/assets/6474beec-1e3d-4b2a-8fd6-250deea2f70e" /> **Additional context** **Have you tried these troubleshooting steps?** - [x] Reviewed [client troubleshooting](https://docs.netbird.io/how-to/troubleshooting-client) (if applicable) - [x] Checked for newer NetBird versions - [x] Searched for similar issues on GitHub (including closed ones) - [x] Restarted the NetBird client - [x] Disabled other VPN software - [x] Checked firewall settings
saavagebueno added the triage-needed label 2026-08-05 02:05:55 -04:00
Author
Owner

@jnfrati commented on GitHub (Apr 21, 2026):

@axi92 just to double check, when you go to login, you are shown the option to log in with zitadel?

<!-- gh-comment-id:4289276494 --> @jnfrati commented on GitHub (Apr 21, 2026): @axi92 just to double check, when you go to login, you are shown the option to log in with zitadel?
Author
Owner

@axi92 commented on GitHub (Apr 21, 2026):

No I switched to EntraID and it also shows that entraID IdP login button and my entra ID login also works.
It looks like my new entra ID account needs to be approved by the netbird admin of my instance. But I am the netbird admin and I dont have any other accounts anymore to login...
The only account I had before the migration was the account from zitadel since it came with the initial installation back in November 2025. (there was no internal IdP)
In the sqlite DB is also just one account entry.

Can I manually set my account to "approved" in the store.db?

<!-- gh-comment-id:4289288962 --> @axi92 commented on GitHub (Apr 21, 2026): No I switched to EntraID and it also shows that entraID IdP login button and my entra ID login also works. It looks like my new entra ID account needs to be approved by the netbird admin of my instance. But I am the netbird admin and I dont have any other accounts anymore to login... The only account I had before the migration was the account from zitadel since it came with the initial installation back in November 2025. (there was no internal IdP) In the sqlite DB is also just one account entry. Can I manually set my account to "approved" in the store.db?
Author
Owner

@axi92 commented on GitHub (Apr 21, 2026):

In the table users I have one account with the role = owner and one with the role = user
I guessed since the account with the role use has pending_approval and blocked set to 1 that that is my IdP account. I set pending_approval and blocked to 0 and started the management again.
After that change I was on the onboarding page.

But I want my account to be able to modify all settings. Should my account have the owner role? Or is there another role?

<!-- gh-comment-id:4289471692 --> @axi92 commented on GitHub (Apr 21, 2026): In the table `users` I have one account with the `role = owner` and one with the `role = user` I guessed since the account with the role use has pending_approval and blocked set to 1 that that is my IdP account. I set `pending_approval` and `blocked` to `0` and started the management again. After that change I was on the onboarding page. But I want my account to be able to modify all settings. Should my account have the `owner` `role`? Or is there another role?
Author
Owner

@axi92 commented on GitHub (Apr 21, 2026):

I was able to set my user to 'admin' so I can manage my instance again.

Now I found out that the old Zitadel ADmin user was migrated to EntraID user but since that mail does not exist on entraID there is no way to login.
Maybe mention that in the migration guide that the existing admin account-email needs to be in the new IdP? Or am I wrong and thats supposed to work different?

Image

You can see the entraId logo on the user.

So what I will do now is to create a new local user. Then stop the management. Change the users role in the db to Owner and store the credentials to that user save in our vault.

<!-- gh-comment-id:4289553907 --> @axi92 commented on GitHub (Apr 21, 2026): I was able to set my user to 'admin' so I can manage my instance again. Now I found out that the old Zitadel ADmin user was migrated to EntraID user but since that mail does not exist on entraID there is no way to login. Maybe mention that in the migration guide that the existing admin account-email needs to be in the new IdP? Or am I wrong and thats supposed to work different? <img width="1597" height="139" alt="Image" src="https://github.com/user-attachments/assets/6d159234-4152-4e37-a0ca-c8efc71e5a48" /> You can see the entraId logo on the user. So what I will do now is to create a new local user. Then stop the management. Change the users role in the db to Owner and store the credentials to that user save in our vault.
Author
Owner

@jnfrati commented on GitHub (Apr 21, 2026):

Right, you're not supposed to switch the provider during the migration. If you were using Zitadel before, the idea was to finish the migration with Zitadel first, and then enable Entra through the dashboard after.

What happened is that the migration script assumed your Zitadel Admin user existed in Entra, and rewrote their user id to work with Entra. But Zitadel and Entra generate different user ids, so now the id in the database doesn't match either system.

Creating the Zitadel user in Entra won't fix it either, because Entra will assign it a new Object ID that won't match what's already stored.

You can confirm this by decoding the user id in the database:

sqlite3 $VOLUME_PATH/store.db "SELECT id FROM users WHERE role = \"owner\"" | base64 -d

You should see a string that's a mix of the Zitadel ID for that user + the id of the static connector you created.


A bit more in-depth: we use Dex as the embedded IdP, so there's basically a chain of OIDC providers. When you request a token from the dashboard, you're really asking our integrated version of Dex for authentication. Dex then asks the third-party provider (Entra or Zitadel) for a token, and uses the "sub" field from that token as the identifier for your user.

Once that's done, Dex returns the "sub" + the connector id to the dashboard as a base64-encoded string, this is what you see as the id in store.db.

Previously, the IdpManager used the "sub" directly without any encoding. So what the migration script does is take the old user.id in store.db, append the static connector id, encode it to base64, and update the references. It's not intelligent enough to tell whether that user id actually came from Entra or Zitadel.

What happened in your case is that you basically told Dex the Zitadel Admin is going to log in using Entra, which will never work. The "sub" that Zitadel generated will never match the "sub" that Entra would generate, even if you created a user that matches 1-to-1 on most fields.


Sorry if the documentation didn't make this clear, does this make more sense now?

<!-- gh-comment-id:4290198281 --> @jnfrati commented on GitHub (Apr 21, 2026): Right, you're not supposed to switch the provider during the migration. If you were using Zitadel before, the idea was to finish the migration with Zitadel first, and then enable Entra through the dashboard after. What happened is that the migration script assumed your Zitadel Admin user existed in Entra, and rewrote their user id to work with Entra. But Zitadel and Entra generate different user ids, so now the id in the database doesn't match either system. Creating the Zitadel user in Entra won't fix it either, because Entra will assign it a new Object ID that won't match what's already stored. You can confirm this by decoding the user id in the database: `sqlite3 $VOLUME_PATH/store.db "SELECT id FROM users WHERE role = \"owner\"" | base64 -d` You should see a string that's a mix of the Zitadel ID for that user + the id of the static connector you created. --- A bit more in-depth: we use Dex as the embedded IdP, so there's basically a chain of OIDC providers. When you request a token from the dashboard, you're really asking our integrated version of Dex for authentication. Dex then asks the third-party provider (Entra or Zitadel) for a token, and uses the "sub" field from that token as the identifier for your user. Once that's done, Dex returns the "sub" + the connector id to the dashboard as a base64-encoded string, this is what you see as the id in store.db. Previously, the IdpManager used the "sub" directly without any encoding. So what the migration script does is take the old user.id in store.db, append the static connector id, encode it to base64, and update the references. It's not intelligent enough to tell whether that user id actually came from Entra or Zitadel. What happened in your case is that you basically told Dex the Zitadel Admin is going to log in using Entra, which will never work. The "sub" that Zitadel generated will never match the "sub" that Entra would generate, even if you created a user that matches 1-to-1 on most fields. --- Sorry if the documentation didn't make this clear, does this make more sense now?
Author
Owner

@axi92 commented on GitHub (Apr 22, 2026):

To avoid confusion for others wouldn't it be good to make a notice in the migration guide that the IdP can not change during the migration and can be added later once the migration is done?

Know that I know that information I could guess that somebody could infer that information from that part: After migration, existing users keep logging in through the same external provider

But I think the documentation should have a warning box on Step3 with a content like:

Warning

Switching the Identity Provider (IdP) during migration is not supported.
The migration script assumes users exist in both the old and new IdP with matching identifiers,
but different IdPs (e.g., Zitadel to Entra ID) generate different user IDs.
As a result, the database ends up with a mismatched ID that works with neither system.

I can open a PR if thats an option for you?

<!-- gh-comment-id:4294388026 --> @axi92 commented on GitHub (Apr 22, 2026): To avoid confusion for others wouldn't it be good to make a notice in the migration guide that the IdP can not change during the migration and can be added later once the migration is done? Know that I know that information I could guess that somebody could infer that information from that part: [`After migration, existing users keep logging in through the same external provider`](https://github.com/netbirdio/docs/blob/5c13dd3a49da867c7ce7627888e691653eb9b62b/src/pages/selfhosted/migration/external-to-embedded-idp.mdx?plain=1#L27) But I think the documentation should have a warning box on Step3 with a content like: > [!WARNING] > Switching the Identity Provider (IdP) during migration is not supported. > The migration script assumes users exist in both the old and new IdP with matching identifiers, > but different IdPs (e.g., Zitadel to Entra ID) generate different user IDs. > As a result, the database ends up with a mismatched ID that works with neither system. I can open a PR if thats an option for you?
Sign in to join this conversation.
No Label triage-needed
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#12447