Skip to content
  • There are no suggestions because the search field is empty.

Trickle (Just-In-Time) User Migration to ZITADEL with Actions

Migrate users on login—no password reset, no bulk dump required.

When you can’t bulk-migrate users (for example, you don’t have password hashes or they use an unsupported algorithm), a trickle—also called lazy or JIT—migration lets each user move the first time they sign in. This guide shows how to implement that pattern with ZITADEL Actions v2 so users authenticate as usual—no mass password resets—while their profiles are transparently created (and their new ZITADEL password set) on first login.

Reference implementation: Check our Actions V2 + Cloudflare Workers utility and read the JIT-USERS-MIGRATION.md guide. It exposes endpoints to handle user lookup and session/password checks so you can plug your legacy store into ZITADEL’s login flow.

When to use trickle migration

Use this pattern if any of these apply:

  • You can’t export password hashes from your legacy IdP/DB.

  • You have unsupported hashing algorithms.

  • Your user base is large and a “big bang” migration would be disruptive.

  • You prefer to migrate users the moment they next log in.

How the flow works (high level)

  1. Users start login on ZITADEL’s hosted Login v2 and enter their username/email.

  2. If the user already exists in ZITADEL, no migration is needed; the login proceeds normally.

  3. If the user doesn’t exist, ZITADEL calls your Actions (webhook) that talk to your Legacy Adapter API.

  4. The adapter fetches the legacy profile and creates the user in ZITADEL (no password yet).

  5. ZITADEL then prompts for password. Your adapter validates that password against your legacy system.

  6. If valid, the adapter updates the user in ZITADEL with a new password and the login completes.

  7. Next time, the user exists in ZITADEL and flows through normally.

This flow maps directly to specific ZITADEL RPCs that Actions can intercept (ListUsers for existence; SetSession for password check).

What your Action hooks intercept

You’ll configure a Response-type Action (target type restCall) for:

  • /zitadel.user.v2.UserService/ListUsers
    Purpose: Detect whether the user already exists. If not, fetch the legacy profile and create the user in ZITADEL, then rewrite the ListUsers response so ZITADEL proceeds as if the user exists.

You’ll configure a Request-type Action (target type restWebhook) for:

  • /zitadel.session.v2.SessionService/SetSession
    Purpose: Validate the password against your legacy system. If valid, update the ZITADEL user with a ZITADEL-managed password, then let the login continue.

Reference action targets code

Use it as a starting point and plug in your legacy storage (DB, LDAP, custom API). The provided example uses a hardcoded list of legacy users for testing purposes.

Detailed flow (step by step)

1) Username step → ListUsers Response Action
  • User types username/email in Login v2.

  • ZITADEL calls /user.v2.UserService/ListUsers internally to check existence.

  • Your Response Action receives the response. If ZITADEL didn’t find a user:

    1. Call your Legacy Adapter to fetch the user profile (e.g., given/family name, email, verified flags).

    2. Create a user in ZITADEL (no password yet) via POST /v2/users/new with the returned profile.

    3. Rewrite the ListUsers response payload to include the newly created user, so the login continues as if the user already existed.

2) Password step → SetSession Request Action
  • ZITADEL prompts the user for their password.

  • ZITADEL calls /session.v2.SessionService/SetSession to add the password check to the session.

  • Your Request Action:

    1. Extracts loginName, userId and the plaintext password (present in the Action payload).

    2. Calls your Adapter/API to validate the credentials against the legacy IdP/DB.

    3. If valid, updates the user in ZITADEL via PATCH /v2/users/:id to set a ZITADEL-managed password.

    4. Return success to ZITADEL so the login completes and tokens are issued.

On subsequent logins, the user already exists and has a ZITADEL password, so Actions won’t need to run. The Actions set a metadata flag on migration to short-circuit any redundant work.

Prerequisites

  • A ZITADEL instance, target organization for the migration, and having the Login v2 enabled.

  • Actions v2 targets (HTTP webhook - see the linked guide above) reachable from ZITADEL.

  • Service user (client credentials or PAT) with permissions to create/update users and sessions.

FAQ

Will users need to reset their password?
No. On their first login, the legacy password is validated and then set in ZITADEL; subsequent logins use ZITADEL directly.

Can I migrate MFA factors?
This pattern focuses on username/password. You can prompt users to (re) enroll MFA post-migration.

What happens if the legacy system is unreachable?
Return a friendly error so the user can retry. Your adapter should implement sensible timeouts and backoff.