Migration From Duende IdentityServer


This document will help you migrate from Duende IdentityServer to FusionAuth.

This guide assumes you have installed FusionAuth. If you have not, please view our installation guides and install FusionAuth before you begin. For more general migration information, please view the FusionAuth migration guide.

There are a number of different ways applications can be integrated with Duende IdentityServer and it would be difficult to cover them all. This guide mentions the typical parts of a bulk migration and focuses on migrating user data from a Duende IdentityServer user database into FusionAuth.

Planning Considerations

Obtaining User Data

Duende IdentityServer does not prescribe where you store your user data. You can store it in a database of your choice, in a file, or in a custom data store. How you store your user information will determine how you export it. This guide assumes your Duende IdentityServer installation uses ASP.NET Identity to manage users and passwords stored in a Microsoft SQL Server database. If you are using another custom setup, this guide should still be helpful as a starting point, but you will need to modify the export program to match your setup.

Mapping User Attributes

The attributes of the User object in FusionAuth are well documented.

If there is an attribute in your Duende IdentityServer user which cannot be directly mapped to a FusionAuth attribute, you can place it in the user.data field. This field can store arbitrary JSON values and will be indexed and searchable.

Social Logins

Duende IdentityServer also provides integrations with other social login providers such as Twitter, Google or Facebook. Review the supported FusionAuth Identity Providers to ensure your social providers are supported.

If not supported explicitly, a provider may work with an OIDC or SAML connection. Otherwise, please open a feature request.

When migrating social logins, you may need to modify the switches of the Duende IdentityServer import script. See Use the Script for more.

Migrating users with social logins such as Apple or Facebook requires that you have an existing user Id for that provider. What this unique user Id looks like depends on the particular social identity provider. The unique Id may be an email address, an integer, UUID, or a random string.

Configure the appropriate FusionAuth Identity Provider with the same values (client_id, etc) as the original user management system you are migrating away from.

Import users with the Import API, assigning each user with a social login a random password such as a UUID.

Your next step depends on whether the social login provider’s unique identifier is available as part of your migration data. If you have the social login provider’s unique identifier, for each user, use the Link API to create a link with the appropriate User Id, Identity Provider Id and Identity Provider User Id.

  • The User Id is the Id of the recently created FusionAuth User.
  • The Identity Provider Id is found on the corresponding Identity Provider API documentation. Look for identityProvider.id .
  • The Identity Provider User Id is the existing social provider user identifier exported or otherwise extracted from the original system.

You do not need to migrate the social network token, which may or may not be accessible. During the first login of a newly migrated user, FusionAuth finds the unique user in the social login provider based on the migrated Identity Provider User Id, and completes the login. During this process, FusionAuth stores a token on the Link, if the social provider returns one. Depending on the configuration of the social provider, users may see a prompt asking if they want to allow FusionAuth to have access to user data such as email address.

IdP Linking Strategies are available since version 1.28.0. Before that version, users were linked on email.

If you do not have the social login provider’s identifier, you need to decide if you want to transparently link the two accounts, which is easier for the end user, or if you want to ask the user to manually link the accounts, which is more accurate, but may be confusing.

To transparently link the accounts, choose a linking strategy of Link On Email or Link On Username, which will create the user if they don’t exist. However, if the user has an email address at their social provider which differs from the email address that was used to sign up for your application and which you imported to FusionAuth, then two accounts will be created.

For example, if the user has a Google account richard@gmail.com, but signed up for your application with richard@fusionauth.io, then if you use the Link On Email strategy, two different accounts will be created, since FusionAuth is trying to match on email address and they don’t. The same holds true for usernames with the Link on Username strategy.

To prompt the user to link the accounts, choose a linking strategy of Pending, which will prompt the end user to sign into FusionAuth after they sign into the social provider, authoritatively linking the two accounts.

Here’s more information about IdP Linking Strategies.

Other Entities

There are often other important entities, such as connections or roles, that need to be migrated. There are usually fewer of these, so an automated migration may not make sense, but plan to move this configuration somehow.

Be aware that functionality may not be the same between Duende IdentityServer and FusionAuth. This is different from user data; as long as you can somehow migrate a login identifier (a username or email) and a password hash, a user will be authenticated and successfully migrated. You can download FusionAuth before you begin a migration and build a proof of concept to learn more about the differences.

A partial list of what may need to be migrated for your application to work properly includes the following:

  • In Duende IdentityServer, External Identity Providers are a source of data for users. FusionAuth calls these Identity Providers.
  • Login flows with Duende IdentityServer are highly customizable using custom code in your host application. FusionAuth authentication flows can be customized with a feature called Lambdas.
  • Duende IdentityServer has the concept of clients. Clients are sets of credentials and authentication flow settings. FusionAuth refers to these as Applications.
  • FusionAuth Tenants are a high-level construct that groups entities such as users and applications together. Duende IdentityServer does not have an equivalent concept. Each Duende IdentityServer deployment is in effect a tenant.
  • For Duende IdentityServer, roles can be implemented through connecting to .Net IdentityStore or through custom code. FusionAuth roles are defined on an application-by-application basis.
  • Refresh tokens allow JWTs to be refreshed without a user logging in. These can be migrated using the Import Refresh Tokens API.
  • Since Duende IdentityStore is a framework used to build custom authentication servers, the actual functionality and features of the particular custom server you are migrating from could vary widely. FusionAuth may not have equivalents for these custom features and functionality, and is focused primarily on authentication, authorization and user management. If we don’t have what you are looking for, please file a feature request with details.

In Duende IdentityServer, users have access to all clients on the same server by default.

In FusionAuth, users are explicitly mapped to applications with a Registration.


When creating an object with the FusionAuth API, you can specify the Id. It must be a UUID.

This works for users, applications, and tenants, among others.

Duende IdentityServer implementations may use any format for an identifier. If you’re managing users with ASP.NET, this identifier may be a UUID, which can be imported when you create the user on FusionAuth.

If you have an IdentityServer implementation that does not use a UUID as the user identifier, you can add a new attribute under user.data (such as user.data.originalId ) with the value of the Duende IdentityServer identifier. Because everything under user.data is indexed and available via search, you’ll be able to find your users using either a new FusionAuth UUID Id or the original Duende IdentityServer identifier.

Login UI

Duende IdentityServer does not prescribe a particular login UI. Login pages are highly variable across Duende IdentityServer implementations.

FusionAuth’s login experience follows two paths. You can choose to build your own login pages or use FusionAuth’s hosted login pages. Read more about these choices.

Exporting Users

Duende IdentityServer provides a package to integrate with ASP.NET Identity, which determines the format user information is stored in.

We recommend exporting your user information in JSON format so that you can import the data into FusionAuth using the FusionAuth API. Alternatively, you can create a custom tool to read your user attributes from the Duende IdentityServer database and directly call the FusionAuth API to create users.

Create a User File

We’ve created a sample C# export program to export users from a Duende IdentityServer database to a JSON file. This program assumes you are using ASP.NET Identity. You can find the folder with the source code for the export program here.

Clone the repo and open the solution in Visual Studio Code.

Update the connectionString variable in the Program.cs file with your database connection information.

The export program assumes that user data is stored in the standard ASP.NET Identity tables, namely AspNetUsers and AspNetUserLogins. If you are using a different table or have extended the ASP.NET Identity tables, you will need to update the program to select from the correct tables. You can do this by modifying the SqlCommand in the Program.cs file. You can also modify the mapping of the user information to the User export objects in the Program.cs file.

The program also assumes that you have used ASP.NET Identity to create the users’ password hashes. ASP.NET Identity stores the salt, hash, and iteration in a single Base64-encoded string. There are two versions of hashing used, as you can see in the ASP.NET Identity source code.

  • V2 - PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations. The format of the Base64-encoded hash string is { 0x00, salt, subkey }. The first byte is a version marker, 0x00 representing V2. The next 128 bits are the salt, and the remaining 256 bits are the subkey or salted password hash. The iteration count is hardcoded to 1000.
  • V3 - PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. The format of the Base64-encoded hash string is a little more complicated: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }. The first byte is a version marker, 0x01 representing V3.

The export program will detect which version of hashing is used and parse out the salt, hash, iterations, and algorithm. The exported users.json file has a property named encryptionScheme . For V2, the encryptionScheme value will be set to example-asp-identity-v2. For V3, the algorithm will be set to salted-pbkdf2-hmac-sha256.

The ASP.NET Identity V3 hashing algorithm is directly supported by FusionAuth.

The ASP.NET Identity V2 hashing algorithm is not directly supported by FusionAuth. If you have V2 hashes, you will need to install a custom hashing plugin in FusionAuth. This is covered in detail in the Add the ASP.NET Identity V2 Hashing Plugin section further on in this guide.

If you have not used ASP.NET Identity or if you have created your own password hashing algorithm, you will need to update the export program to use this hashing algorithm and create a custom hashing plugin for FusionAuth if necessary. FusionAuth supports these hashing algorithms out of the box, so you can check if your algorithm is already supported.

Once you have customized the export program to your Duende IdentityServer implementation, run it. It will output a users.json file in the same directory as the running program, usually the bin/Debug/net6.0 folder.

Once the export is complete, you should have a JSON file called users.json containing user objects that look something like this:

  "users": [
      "active": true,
      "birthDate": null,
      "insertInstance": 1680463413000,
      "data": {
        "migrated": true,
        "favoriteColors": "red"
      "email": "richard@example.com",
      "expiry": null,
      "firstName": "",
      "fullName": "",
      "id": "004eba25-8f8a-4571-ac02-d4d618fa0e28",
      "lastLoginInstant": 0,
      "lastName": "",
      "middleName": "",
      "mobilePhone": "",
      "password": "kYvylojznO+rLOZm+Y0r3q52aFrtIwJucApZY8HF/wk=",
      "salt": "dFilBnTpkMaB5NnPGEaBTg==",
      "factor": 10000,
      "encryptionScheme": "salted-pbkdf2-hmac-sha256",
      "passwordChangeRequired": false,
      "passwordLastUpdateInstant": 0,
      "preferredLanguages": [
      "identityProviders": {},
      "timezone": null,
      "twoFactorEnabled": false,
      "username": "alice",
      "verified": true

Check the output for any errors and to make sure the data looks correct. If you have any issues, you can modify the export program to output more information about the users being exported.

Also check if there are any encryptionScheme values that are set to example-asp-identity-v2. If there are, you will need to create a custom hashing plugin for FusionAuth. This is covered in detail in the Add the ASP.NET Identity V2 Hashing Plugin section further on in this guide.

Importing Users

Next, import the user data. Here are the steps we need to take.

  1. Set Up FusionAuth.
  2. Get the Script.
  3. Install Needed Gems.
  4. Use the Script.
  5. Verify the Import.
  6. The Final Destination of Imported Users.

Set Up FusionAuth

You need to set up FusionAuth so migrated user data can be stored. As mentioned above, this guide assumes you have FusionAuth installed.

If you don’t, view our installation guides and install it before proceeding further.

Create a Test Tenant

It is best to create a separate tenant for migration testing. Tenants logically isolate configuration settings and users. If a migration goes awry or you need to redo it after tweaking settings, you can delete the test tenant and start with a clean system. To add a tenant, navigate to Tenants and choose the Add button (green plus sign).

Adding a tenant.

Give it a descriptive Name like Duende IdentityServer import test. You shouldn’t need to modify any of the other configuration options to test importing users.

Save the tenant.

The tenant creation screen.

Record the Id of the tenant, which will be a UUID. It will look something like 25c9d123-8a79-4edd-9f76-8dd9c806b0f3. You’ll use this later.

The tenant list.

Create a Test Application

Applications are anything a user can log in to. In FusionAuth there’s no differentiation between web applications, SaaS applications, APIs and native apps. To add an application, navigate to Applications and click on the Add button (the green plus sign). Give the application a descriptive name like Duende IdentityServer application.

Select your new tenant, created above, in the dropdown for the Tenant field.

Navigate to the OAuth tab and add an entry to Authorized redirect URLs . Use a dummy value such as https://example.com. Later, you’ll need to update this to be a valid redirect URL that can take the authorization code and exchange it for a token. Learn more about this in the FusionAuth OAuth documentation.

You shouldn’t need to modify any of the other configuration options to test importing users. Save the application.

The application creation screen.

Next, view the application by clicking the green magnifying glass and note the OAuth IdP login URL . You’ll be using it to test that users can log in.

Finding the login URL.

Add an API Key

The next step is to create an API key. This will be used by the import script. To do so, navigate to Settings -> API Keys in the administrative user interface.

Adding an API key

This key needs to have the permission to run a bulk import of users. In the spirit of the principle of least privilege, give it only the following permissions:

  • POST to the /api/user/import endpoint.
  • POST to the /api/user/search endpoint.
  • POST to the /api/identity-provider/link endpoint (not shown below).

Record the API key string, as you’ll use it below.

Setting API key permissions

Add the ASP.NET Identity V2 Hashing Plugin

Some older IdentityServer systems using ASP.NET Identity may still have password hashes created by the V2 version of the ASP.NET Identity hashing scheme. FusionAuth does not support this scheme directly, but does support plugins for custom hashing.

FusionAuth has a repository for community-contributed examples and code. This repo contains a password hash plugin called ExampleASPIdentityV2PasswordEncryptor. To extend FusionAuth to support ASP.NET Identity V2 hashes, follow the instructions in the article writing a plugin using the plugin in the contributed examples. A quick summary of the steps:

  • Clone the community-contributed examples and code repository.
  • Navigate to the Password Hashing Plugins directory.
  • Install Java JDK 8 or higher, for example, from https://jdk.java.net/20/.
  • Install Maven.
  • Install Savant.
  • Run mvn install to test that the plugin builds.
  • Run mvn clean compile package to create the plugin JAR file.
  • Copy the plugin .jar file from the ./target directory to the plugins directory of your FusionAuth installation. ** On Linux and macOS, the plugin directory is /usr/local/fusionauth/plugins. ** On Windows, the plugin directory is \fusionauth\plugins.
  • Restart FusionAuth.

After installing the plugin and restarting FusionAuth, you can verify that the plugin is installed by navigating to Tenants -> Your Tenant, then the Password tab. Under the Cryptographic hash settings section, you should see the new hash scheme available in the Scheme dropdown. Only check that the plugin is listed, don’t select it or any new user will be created with that hash scheme. Rather than selecting the plugin here, it is specified per user in the users.json file.

If the plugin doesn’t show up, please review the plugin troubleshooting steps.

Get the Script

FusionAuth provides an import script under a permissive open source license. It requires ruby (tested with ruby 2.7). To get the script, clone the git repository:

Getting the import scripts

git clone https://github.com/FusionAuth/fusionauth-import-scripts

Navigate to the duende directory:

cd fusionauth-import-scripts/duende

Install Needed Gems

The following gems must be available to the import script:

  • date
  • json
  • optparse
  • securerandom
  • fusionauth_client

It is likely that all of these will be on your system already, except the fusionauth_client gem.

If you have bundler installed, run bundle install in the duende directory. Otherwise, install the needed gems another way.

Use the Script

You can see the output of the script by running it with the -h option:

Running the import script with the help command line switch

ruby ./import.rb -h

The output will be similar to this:

The help output of the import.rb script

Usage: import.rb [options]
    -l, --link-social-accounts       Link social accounts, if present, after import. This operation is slower than an import.
    -r APPLICATION_IDS,              A comma separated list of existing applications Ids. All users will be registered for these applications.
    -o, --only-link-social-accounts  Link social accounts with no import.
    -u, --users-file USERS_FILE      The exported JSON user data file from IdentityServer. Defaults to users.json.
    -f FUSIONAUTH_URL,               The location of the FusionAuth instance. Defaults to http://localhost:9011.
    -k, --fusionauth-api-key API_KEY The FusionAuth API key.
    -t TENANT_ID,                    The FusionAuth tenant id. Required if more than one tenant exists.
    -h, --help                       Prints this help.

For this script to work correctly, set the following switches, unless the defaults work for you:

  • -u should point to the location of the user export file you obtained, unless the default works.
  • -f should point to your FusionAuth instance. If you are testing locally, it will probably be http://localhost:9011.
  • -k should be set to the value of the API key created above.
  • -t should be set to the Id of the testing tenant created above.

The -o and -l switches will attempt to create links for any users authenticated via Google or another social identity provider found in the users data file.

If you are loading users with social account authentication, you must create the social identity providers in FusionAuth beforehand or the links will fail. Additionally, creating a link is not currently optimized in the same way that loading a user is. It may make sense to import all the users in one pass (omitting the -l switch) and then create the links using the -o switch in a second pass, after the users are imported.

The social account linking functionality will only work with FusionAuth version 1.28 or above. The fusionauth_client library must be >= 1.28.

When you run the script, you should get an output similar to the following:

Import script output

$ ruby ./import.rb -f http://localhost:9011 -k '...' -t '...' -u users.json
FusionAuth Importer : IdentityServer
 > User file: users.json
 > Call FusionAuth to import users
 > Import success
Duplicate users 0
Import complete. 2 users imported.

Enhancing the Script

You may want to migrate additional data. Currently, the following attributes are migrated:

  • user_id
  • email
  • email_verified
  • username
  • insertInstance
  • the password hash and supporting attributes, if available
  • registrations, if supplied

The migrated user will have the original Duende IdentityServer user Id. If you have additional user attributes to migrate, review and modify the map_user method.

You may also want to assign Roles or associate users with Groups by creating the appropriate JSON data structures in the import call. These are documented in the Import User API docs. This will require modifying the import.rb code.

Verify the Import

Next, log in to the FusionAuth administrative user interface. Review the user entries to ensure the data was correctly imported.

List imported users.

You can manage the user by clicking on the Manage button (black button) to the right of the Created date in the list to review the details of the imported user’s profile.

If you have a test user whose password you know, open an incognito window and log in to ensure the hash migration was successful. You recorded the URL to log in to the example application in Create a Test Application .

The user login screen.

After the test login, the user will be redirected to a URL like https://example.com/?code=FlZF97WIYLNxt4SGD_22qvpRh4fZ6kg_N89ZbBAy1E4&locale=fr&userState=Authenticated. This happens because you haven't set up a web application to handle the authorization code redirect.

That is an important next step but is beyond the scope of this document. Consult the 5 minute setup guide

for an example of how to do this.

The Final Destination of Imported Users

After you are done testing, you can choose to import users into the default tenant or a new tenant. Whichever you choose, make sure to update the -t switch to the correct value before running the import for the final time.

If you aren’t keeping users in the test tenant, delete it.

If you need to start over because the import failed or you need to tweak a setting, delete the tenant you created. This will remove all the users and other configurations for this tenant, giving you a fresh start. To do so, navigate to Tenants and choose the Delete button (red trash can icon).

Deleting a tenant.

Confirm your desire to delete the tenant. Depending on how many users you have imported, this may take some time.

What to Do Next

You now have your users migrated, or a plan to do so. Congratulations! What is next?

You need to migrate additional configurations, as mentioned in Other Entities . Since the type of configuration varies, it is hard to provide a full list of how to import these items, but the general pattern will be:

  • Identify corresponding FusionAuth functionality.
  • Configure it in your FusionAuth instance, either manually or by scripting it using the client libraries or API.
  • Update your application configuration to use the new FusionAuth functionality.

Make sure you assign your users to the appropriate FusionAuth applications. You can do this either:

  • As part of your import process by adding registrations at import time.
  • After users have been migrated with the Registrations API.

You’ll also need to modify and test each of your applications, whether custom, open source, or commercial, to ensure:

  • Users can successfully log in.
  • The authorization code redirect is handled correctly.
  • Users receive appropriate permissions and roles based on the JWT.
  • The look and feel of the hosted login pages matches each application’s look and feel.

If your application uses a standard OAuth, SAML or OIDC library to communicate with , the transition should be relatively painless.

Additional Support

If you need support in your migration beyond that provided in this guide, you may: