Skip to main content

Migrate Users

Migrating users from an existing system, while minimizing impact on said users, can be a challenging task.

Individual Users​

Creating individual users can be done with this endpoint: ImportHumanUser. Please also consult our guide on how to create users.

{
"userName": "test9@test9",
"profile": {
"firstName": "Road",
"lastName": "Runner",
"displayName": "Road Runner",
"preferredLanguage": "en"
},
"email": {
"email": "test@test.com",
"isEmailVerified": false
},
"hashedPassword": {
"value": "$2a$14$aPbwhMVJSVrRRW2NoM/5.esSJO6o/EIGzGxWiM5SAEZlGqCsr9DAK",
"algorithm": "bcrypt"
},
"passwordChangeRequired": false,
"otpCode": "testotp",
"requestPasswordlessRegistration": false,
"idps": [
{
"configId": "124425861423228496",
"externalUserId": "roadrunner@mailonline.com",
"displayName": "name"
}
]
}

Bulk import​

For bulk import use the import endpoint on the admin API:

{
"timeout": "10m",
"data_orgs": {
"orgs": [
{
"orgId": "104133391254874632",
"org": {
"name": "ACME"
},
"humanUsers": [
{
"userId": "104133391271651848",
"user": {
"userName": "test9@test9",
"profile": {
"firstName": "Road",
"lastName": "Runner",
"displayName": "Road Runner",
"preferredLanguage": "de"
},
"email": {
"email": "test@acme.tld",
"isEmailVerified": true
},
"hashedPassword": {
"value": "$2a$14$aPbwhMVJSVrRRW2NoM/5.esSJO6o/EIGzGxWiM5SAEZlGqCsr9DAK",
"algorithm": "bcrypt"
}
}
},
{
"userId": "120080115081209416",
"user": {
"userName": "testuser",
"profile": {
"firstName": "Test",
"lastName": "User",
"displayName": "Test User",
"preferredLanguage": "und"
},
"email": {
"email": "fabienne@caos.ch",
"isEmailVerified": true
},
"hashedPassword": {
"value": "$2a$14$785Fcdbpo9rn5L7E21nIAOJvGCPgWFrZhIAIfDonYXzWuZIKRAQkO",
"algorithm": "bcrypt"
}
}
},
{
"userId": "145195347319252359",
"user": {
"userName": "wile@test9",
"profile": {
"firstName": "Wile E.",
"lastName": "Coyote",
"displayName": "Wile E. Coyote",
"preferredLanguage": "en"
},
"email": {
"email": "wile.e@acme.tld"
}
}
}
]
}
]
}
}
info

We will improve the bulk import interface for users in the future. You can show your interest or join the discussion on this issue.

Migrate secrets​

Besides user data you need to migrate secrets, such as password hashes, OTP seeds, and public keys for passkeys (FIDO2). The snippets in the sections below are parts from the bulk import endpoint, to clarify how the different objects can be imported.

Passwords​

Passwords are stored only as hash. You can transfer the hashes as long as ZITADEL supports the same hash algorithm. Password change on the next sign-in can be enforced.

snippet from bulk-import example:

{
"userName": "test9@test9",
...,
"hashedPassword": {
"value": "$2a$14$aPbwhMVJSVrRRW2NoM/5.esSJO6o/EIGzGxWiM5SAEZlGqCsr9DAK",
"algorithm": "bcrypt"
},
"passwordChangeRequired": false,
...,
}

In case the hashes can't be transferred directly, you always have the option to create a user in ZITADEL without password and prompt users to create a new password.

If your legacy system receives the passwords in clear text (eg, login form) you could also directly create users via ZITADEL API. We will explain this pattern in more detail in this guide.

info

In case the hash algorithm you are using is not supported by ZITADEL, please let us know after searching our discussions, issues, and chat for similar requests.

One-time-passwords (OTP)​

You can pass the OTP secret when creating users:

snippet from bulk-import example:

{
"userName": "test9@test9",
...,
"otpCode": "testotp",
...,
}

Passkeys​

When creating new users, you can trigger a workflow that prompts the users to setup a passkey authenticator.

snippet from bulk-import example:

{
"userName": "test9@test9",
...,
"requestPasswordlessRegistration": false,
...,
}

For passkeys to work on the new system you need to make sure that the new auth server has the same domain as the legacy auth server.

info

Currently it is not possible to migrate passkeys directly from another system.

Users linked to an external IDP​

A users sub is bound to the external IDP's Client ID. This means that the IDP Client ID configured in ZITADEL must be the same ID as in the legacy system.

Users should be imported with their externalUserId.

snippet from bulk-import example:

{
"userName": "test9@test9",
...,
"idps": [
{
"configId": "124425861423228496",
"externalUserId": "roadrunner@mailonline.com",
"displayName": "name"
}
...,
}

You can use an Action with post-creation flow to pull information such as roles from the old system and apply them to the user in ZITADEL.

Metadata​

You can store arbitrary key-value information on a user (or Organization) in ZITADEL. Use metadata to store additional attributes of the users, such as organizational unit, backend-id, etc.

info

Metadata must be added to users after the users were created. Currently metadata can't be added during user creation.
API reference: User Metadata

Request metadata from the userinfo endpoint by passing the required reserved scope in your auth request. With the complement token flow, you can also transform metadata (or roles) to custom claims.

Authorizations / Roles​

You can assign roles from owned or granted projects to a user.

info

Authorizations must be added to users after the users were created. Currently metadata can't be added during user creation.
API reference: User Authorization / Grants