Migrating Dispatch Target From Generic Credentials to Dedicated Entities
In nevisIDM and nevisFIDO 9.2605.0 we introduced a new data model for dispatch targets. The new data model allows to assign dedicated entities to dispatch targets instead of using generic credentials. In nevisFIDO the usage of the old and new entities are controllable by a configuration. This migration guide describes how to migrate existing dispatch targets from the old data model to the new one.
::warning The migration process should be considered irreversible. It is recommended to take a backup of the database before starting the migration,
Depending on the configuration of the migration tool old Generic Credential based dispatch targets will be deleted during the migration process,
Even if they are not deleted, they set to disabled and cannot be used anymore.
Reactivation of old dispatch targets is heavily discouraged, since the possibility of data drift after migration cannot be ruled out. :::
Migration pipeline
Phase 1 — Discover users and classify generic credentials
Steps:
- SCIM filter
fidouaf_name PRdiscovers users with Dispatch Target generic credentials - SCIM filter
ios_app_attestation_public_key PRdiscovers users with App Attestation generic credentials - The union of both user sets is processed; each user's generic credentials are fetched once via the Core API
- Generic credentials are classified: those with
fidouaf_name→ Dispatch Target list; those withios_app_attestation_public_key→ App Attestation list - Each generic credential's
stateNamefield is validated: onlyactiveanddisabledare expected
Possible errors:
USER_CREDENTIALS_LOAD_FAILED— HTTP error fetching a user's generic credentialsUNEXPECTED_CREDENTIAL_STATE— generic credentialstateNameis notactiveordisabled; the credential is skipped
Phase 2 — Pair Dispatch Target generic credentials with App Attestation generic credentials
Steps:
- Dispatch Target generic credentials without
fidouaf_device_idare filtered out - Dispatch Target generic credentials whose
fidouaf_device_idis shared by another Dispatch Target generic credential of the same user are filtered out (the device ID is ambiguous and pairing cannot be determined) - App Attestation generic credentials are indexed by
ios_app_attestation_device_id - Each remaining Dispatch Target generic credential is paired with an App Attestation generic credential by matching device IDs
Possible errors:
MISSING_DEVICE_ID— Dispatch Target generic credential has nofidouaf_device_idDUPLICATE_DEVICE_ID— two or more Dispatch Target generic credentials of the same user share the samefidouaf_device_id; all of them are skipped because pairing is ambiguousUNPAIRED_APP_ATTESTATION— App Attestation generic credential has no matching Dispatch Target generic credential by device ID
Phase 3 — Pair FIDO UAF authenticators with Dispatch Target generic credentials
Steps:
- FIDO UAF authenticators already linked to a DispatchTarget are skipped silently
- Pairing strategy (in order):
- Explicit
dispatchTargetExtIdfield on the FIDO UAF authenticator - Fallback: matching
deviceIdbetween the FIDO UAF authenticator and the Dispatch Target generic credential
- Explicit
Possible errors:
FIDO_AUTHENTICATORS_LOAD_FAILED— HTTP/IO error fetching a user's FIDO UAF authenticatorsMAPPING_VALIDATION_FAILED(cleanup mode only) — FIDO UAF authenticator already has a linked DispatchTarget but no matching Dispatch Target generic credential found bydispatchTargetExtId
Phase 4 — Report orphan Dispatch Target generic credentials
Steps:
- Dispatch Target generic credentials not matched to any FIDO UAF authenticator are identified and reported
Possible errors:
ORPHAN_DISPATCH_TARGET— Dispatch Target generic credential has no matching FIDO UAF authenticator
Phase 5 — Create DispatchTargets and assign FIDO UAF authenticators
The behaviour of this phase is controlled by --gc-handling (default: retain-generic).
retain-generic (default — staged workflow)
For each group of FIDO UAF authenticators sharing a Dispatch Target generic credential:
Steps:
- Create one
DispatchTargetviaPOST .../dispatch-targets; itsstateis forwarded from the source generic credential - Validate the created entity by fetching it and comparing mandatory fields (
identification,appId,name,signingKey,state) and optional fields if present (deviceId,target,dispatcher,encryptionKey,userAgent) - If a paired App Attestation generic credential exists, create it via
POST .../dispatch-targets/{id}/app-attestationand validate it (publicKey,receipt,identification, optionallyenvironment) - Assign the new
DispatchTargetto each FIDO UAF authenticator viaPOST .../fido-authenticators/{id}/dispatch-target/{dtId} - Disable the source Dispatch Target generic credential via
PATCH .../generic-credentials/{id}(setsstateNametodisabled) - If the Dispatch Target generic credential was disabled successfully and a paired App Attestation generic credential exists, disable the App Attestation generic credential the same way
Possible errors:
DISPATCH_TARGET_CREATE_FAILED— one or more mandatory fields missing, or exception during creation; processing stops for this groupDISPATCH_TARGET_VALIDATION_FAILED— fetched entity fields do not match the source generic credential; processing stops (no assignment, no disabling)APP_ATTESTATION_CREATE_FAILED— one or more mandatory fields missing, or exception during creation; processing stops for this groupAPP_ATTESTATION_VALIDATION_FAILED— fetched App Attestation fields do not match the source generic credential; processing stops (no assignment, no disabling)ASSIGNMENT_FAILED— exception during FIDO UAF authenticator assignmentGENERIC_CREDENTIAL_DISABLE_FAILED— exception disabling the source Dispatch Target generic credential; the App Attestation generic credential is not disabled if the Dispatch Target generic credential disable failed
delete-generic
Same as retain-generic (steps 1–4), but after a fully successful group:
Steps:
5. Delete the source Dispatch Target generic credential via DELETE .../generic-credentials/{id}
6. If the Dispatch Target generic credential was deleted successfully and a paired App Attestation generic credential exists, delete the App Attestation generic credential
Possible errors:
- Same as
retain-genericsteps 1–4 (see above) GENERIC_CREDENTIAL_DELETE_FAILED— exception deleting the source generic credential; the App Attestation generic credential is not deleted if the Dispatch Target generic credential deletion failed
delete-only-if-already-exists (cleanup mode)
Used after a previous retain-generic run to remove generic credentials that have already been migrated. Instead of creating new entities, the pipeline:
Steps:
- Find FIDO UAF authenticators that already have a linked
DispatchTargetand pair them with Dispatch Target generic credentials bydispatchTargetExtId - Validate that the existing
DispatchTargetentity matches the Dispatch Target generic credential - If an App Attestation generic credential is present, validate the existing
AppAttestationentity - Delete the source Dispatch Target generic credential
- If the Dispatch Target generic credential was deleted successfully and a paired App Attestation generic credential exists, delete the App Attestation generic credential
Possible errors:
MAPPING_VALIDATION_FAILED— unmatched Dispatch Target generic credential (no FIDO UAF authenticator with matchingdispatchTargetExtId)DISPATCH_TARGET_VALIDATION_FAILED— fetched entity fields do not match the source generic credentialAPP_ATTESTATION_VALIDATION_FAILED— fetched App Attestation fields do not match the source generic credentialGENERIC_CREDENTIAL_DELETE_FAILED— exception deleting the source generic credential; the App Attestation generic credential is not deleted if the Dispatch Target generic credential deletion failed
In simulation mode (--dry-run) all steps — creation, validation, assignment, disabling, and deletion — are logged but not executed.
Error and failure categories
All skipped and failed entities are written to the CSV report.
| Category | Phase | Entity type referenced by entityExtId | Trigger |
|---|---|---|---|
USER_CREDENTIALS_LOAD_FAILED | 1 | user ¹ | HTTP error fetching a user's generic credentials |
UNEXPECTED_CREDENTIAL_STATE | 1 | generic credential | Generic credential stateName (API response field) is not active or disabled |
MISSING_DEVICE_ID | 2 | Dispatch Target generic credential | Dispatch Target generic credential has no fidouaf_device_id |
DUPLICATE_DEVICE_ID | 2 | Dispatch Target generic credential | Two or more Dispatch Target generic credentials of the same user share the same fidouaf_device_id; all are skipped because pairing is ambiguous |
UNPAIRED_APP_ATTESTATION | 2 | App Attestation generic credential | App Attestation generic credential has no matching Dispatch Target generic credential by device ID |
FIDO_AUTHENTICATORS_LOAD_FAILED | 3 | user ¹ | HTTP/IO error fetching a user's FIDO UAF authenticators |
MAPPING_VALIDATION_FAILED | 3 ² | Dispatch Target generic credential | Cleanup mode only: FIDO UAF authenticator already has a linked DispatchTarget but no matching Dispatch Target generic credential found by dispatchTargetExtId |
ORPHAN_DISPATCH_TARGET | 4 | Dispatch Target generic credential | Dispatch Target generic credential has no matching FIDO UAF authenticator |
DISPATCH_TARGET_CREATE_FAILED | 5 | Dispatch Target generic credential | One or more mandatory fields missing (identification, signingKey, appId, name), or exception during POST .../dispatch-targets |
DISPATCH_TARGET_VALIDATION_FAILED | 5 | Dispatch Target generic credential | Fetched entity fields do not match the source generic credential; item processing stops (no assignment, no deletion) |
APP_ATTESTATION_CREATE_FAILED | 5 | App Attestation generic credential | One or more mandatory fields missing (receipt, publicKey, identification), or exception during POST .../app-attestation |
APP_ATTESTATION_VALIDATION_FAILED | 5 | App Attestation generic credential | Fetched App Attestation entity fields do not match the source generic credential; item processing stops (no assignment, no deletion) |
ASSIGNMENT_FAILED | 5 | FIDO UAF authenticator | Exception during FIDO UAF authenticator assignment |
GENERIC_CREDENTIAL_DISABLE_FAILED | 5 | generic credential | Exception disabling the source generic credential (retain-generic mode only); App Attestation generic credential is not disabled if Dispatch Target disable failed |
GENERIC_CREDENTIAL_DELETE_FAILED | 5 | generic credential | Exception deleting the source generic credential (delete-generic or delete-only-if-already-exists mode only); App Attestation generic credential is not deleted if Dispatch Target deletion failed |
¹ For user-level failures entityExtId duplicates userExtId — there is no finer-grained entity to reference.
² In cleanup mode (delete-only-if-already-exists) this check happens during Phase 3 pairing.
CLI options reference
| Option | Required | Default | Description |
|---|---|---|---|
--base-url / -b | yes | — | nevisIDM base URL, e.g. https://idm.example.com/nevisidm/api |
--client-ext-id / -c | yes | — | Client extId(s) to migrate; comma-separated or repeated |
--page-size | no | 200 | Records per page for all paginated API calls |
--dry-run | no | false | Simulate without executing any API writes (creation, validation, assignment, deletion are all logged but not executed) |
--report-file | no | auto | Path for the CSV report (auto-generated timestamp name if omitted) |
--gc-handling | no | retain-generic | Generic credential handling after migration: retain-generic (disable source generic credential; App Attestation generic credential disabled only if Dispatch Target generic credential disable succeeded), delete-generic (delete source generic credential; same conditional applies), delete-only-if-already-exists (cleanup: validate existing mapping and delete generic credentials) |
--keystore | cert auth | — | Path to client PKCS12/JKS keystore |
--keystore-password | no | — | Keystore password. Env var: IDM_MIGRATION_KEYSTORE_PASSWORD. Prompted interactively if absent. |
--keystore-type | no | PKCS12 | PKCS12 or JKS |
--truststore | no | — | Path to truststore for server certificate verification |
--truststore-password | no | — | Truststore password. Env var: IDM_MIGRATION_TRUSTSTORE_PASSWORD. Prompted if truststore is configured and absent. |
--truststore-type | no | PKCS12 | PKCS12 or JKS |
--username / -u | basic auth | — | Username for Basic Auth |
--password / -p | no | — | Basic Auth password. Env var: IDM_MIGRATION_BASIC_AUTH_PASSWORD. Prompted interactively if absent. |
Migration Steps
Preparation
- Download the migration tool from Nevis Portal and extract it to a directory.
- Create a trust- and keystore for the migration tool.
- Create database backup before starting the migration.
Migration
- Stop the nevisFIDO services.
- Run the migration tool in dry run configuration with appropriate CLI options (see above). For example:
java -jar dispatch-target-migration-tool-<<migration took version>>.jar --base-url https://yourdomain.com/nevisidm/api \
--keystore /home/youruser/nevis/nevisidm/dispatchtargetmigration/keystore.p12 \
--keystore-password=keystorepassword \
--truststore /home/youruser/nevis/nevisidm/dispatchtargetmigration/truststore.p12 \
--truststore-password=truststorepassword \
--client-ext-id 100,yourClient \
--report-file /home/youruser/nevis/nevisidm/dispatchtargetmigration/migration_report.csv \
--dry-run
- Review the generated report for any errors or unexpected results. If errors are found, investigate and resolve them before proceeding.
- Run the migration tool again without
--dry-runto execute the migration. - Review the generated report for any errors during execution. If errors are found, investigate and resolve them.
- Restart the nevisFIDO services.
Permissions
Generally we advise to have a ClientRoot or Root user for the migration, since they have permissions to access all required API endpoints and perform all necessary operations. If such a user is not available, here are the list of the endpoints that the user needs to have access to, and the required permissions for each endpoint (please check our API documentation for the exact permissions required for each endpoint):
| Method | Path | Purpose |
|---|---|---|
GET | /scim/v1/{client}/Users?filter=... | Discover users with matching generic credential properties |
GET | /core/v1/{client}/users/{user}/generic-credentials | Fetch all generic credentials for a user |
GET | /core/v1/{client}/users/{user}/fido-authenticators | Fetch all FIDO UAF authenticators for a user |
POST | /core/v1/{client}/users/{user}/dispatch-targets | Create DispatchTarget from generic credential data |
GET | /core/v1/{client}/users/{user}/dispatch-targets/{dt} | Validate created/existing DispatchTarget entity |
POST | /core/v1/{client}/users/{user}/dispatch-targets/{dt}/app-attestation | Create App Attestation |
GET | /core/v1/{client}/users/{user}/dispatch-targets/{dt}/app-attestation | Validate created/existing App Attestation entity |
POST | /core/v1/{client}/users/{user}/fido-authenticators/{fido}/dispatch-target/{dt} | Assign DispatchTarget to FIDO UAF authenticator |
PATCH | /core/v1/{client}/users/{user}/generic-credentials/{gc} | Disable source generic credential (retain-generic mode: sets stateName to disabled) |
DELETE | /core/v1/{client}/users/{user}/generic-credentials/{gc} | Delete source generic credential (delete-generic / delete-only-if-already-exists mode) |