Leaving self-hosted Duende: what migrates, and what you stop operating
Duende IdentityServer is a genuinely good piece of software — if you want to own your identity layer end to end, it's the standard in .NET. But "owning it" is the whole cost: you host it, patch it, scale it, and build everything around it — the admin UI, MFA, audit logs, branding, a status page, user and role management. At some point a team decides it would rather not operate an IdP, and the only question that matters is how painful it is to move. Here's what actually migrates.
What you're actually running
Self-hosting Duende puts you on the hook for more than the token endpoints:
- The IdP itself — hosting, scaling, patching, and the license (SAML is a paid add-on on top of it).
- Everything around it that you built yourself — admin portal, MFA enrolment, audit logging, custom branding, a status page, user/role management.
That second list is usually where the real time goes.
What migrates — and it's mostly mechanical
Duende keeps its config in SQL (the ConfigurationDb) and its users in ASP.NET Identity. A migration reads both:
- Clients → OAuth clients, including redirect and logout URIs, CORS origins, grant types, refresh-token usage/expiration semantics, and device-code lifetimes. Disabled clients import disabled; expired secrets are skipped with a warning.
- Scopes → ApiScopes and IdentityResources map across directly. Duende's
ApiResourcemiddle layer (an audience plus a shared claims list) flattens onto the simpler model: the resource name becomes an audience on every client that uses a member scope, and its claims merge onto those scopes. - Users → the good news: ASP.NET Identity V3 password hashes (and legacy bcrypt) verify natively and rehash on first sign-in. No password resets, no support ticket, nothing your users notice. (This is the part that's genuinely hard when leaving Auth0 — with Duende it just works, because it's the same hashing your app already uses.)
- Roles and assignments, external logins, and OIDC identity providers all come across. SAML providers are flagged for you to reconfigure on the other side.
The identity-stability bit
As with any IdP move, the thing to get right is not changing user sub or client_id — downstream refresh tokens, SCIM rows in customer IdPs, and user IDs stored in your own database all reference them. The importer preserves both. And if one of your portal owners also exists as a Duende user under a different ID, it reconciles them (rotating to the Duende sub) through staged, recoverable steps rather than leaving a half-migrated owner who can't sign in.
An aside on testing migrations (a .NET gotcha)
We test the importer against a real, seeded Duende database, not mocks — and it paid off. ASP.NET Identity stores AspNetUsers.LockoutEnd as a datetimeoffset, and reading it with reader.GetDateTime() throws an InvalidCastException on that type. So a single locked-out user would have failed the entire user import. You only find that by running the importer against real data that has a locked-out user in it. If you're weighing any migration tool, that's the question worth asking: is it tested against a populated database, or just mocked against the source API?
What you stop doing
The point of moving isn't the migration — it's everything after it. SAML, SCIM, MFA, audit logs, custom domains and branding are included rather than things you build and operate, and patching and scaling the IdP stop being your problem. If self-hosting Duende was a deliberate "we want full control" decision and it still is, stay — that's a perfectly good choice. This is for when running an IdP has stopped being where you want to spend your time.
If you're considering it
The migration is mostly mechanical, passwords carry over without resets, and the preview is read-only — point it at your Duende database and it shows you exactly what would import before anything is written.