> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Benutzer migrieren: Passwörter und MFA importieren

> Mit Didit Benutzer samt Passwörtern, verifizierten E-Mails, Social-Links, Authenticator-Apps und Rollen per CSV, JSON oder Stapeln importieren.

> [!WARNING]
>
> Authentication ist neu. Führen Sie jeden Import zuerst in einer Sandbox-Anwendung aus und importieren Sie danach in Ihre Live-Anwendung.

Ein Import überführt Personen von einer anderen Identitätsplattform zu Didit Authentication. Ihr Passwort, ihre verifizierte E-Mail-Adresse, ihre Social-Login-Verknüpfungen, ihre Authenticator-App und ihre Organisationsrollen werden dabei übernommen. Für die Personen ändert sich zunächst nichts, bis Ihre Anwendung sie mit Didit anmeldet.

Importe verwenden die Management-API für Authentication Ihrer Anwendung mit einer Konsolensitzung, die `write:authentication` besitzt (oder `read:authentication`, um Importe aufzulisten). Sie verwenden niemals ein Endbenutzer-Token oder einen Verifizierungs-API-Schlüssel. In den Beispielen lautet `MANAGEMENT_BASE`:

```text theme={null}
https://apx.didit.me/auth/v2/organizations/{org_id}/applications/{application_id}/authentication/
```

## Zwei Importwege

| | Dateiauftrag | Batch-Anfrage |
| - | - | - |
| Endpoint | `POST import-jobs/` (multipart) | `POST imports/` (JSON) |
| Größe | Eine CSV- oder JSON-Datei, bis zu 25 MB und 100.000 Personen | 1 bis 1.000 `rows` pro Anfrage |
| Probelauf | Läuft immer zuerst, im Hintergrund | Standardmäßig `dry_run: true` |
| Übernahme | `POST import-jobs/{id}/start/` | Dieselbe Anfrage mit `dry_run: false` |
| Teilfehler | Jeder Batch mit 1.000 Personen wird einzeln übernommen; ein unterbrochener Import wird fortgesetzt | Alles oder nichts |

## Datei importieren

### 1. Hochladen

```bash theme={null}
curl --fail-with-body "${MANAGEMENT_BASE}import-jobs/" \
  -H "Authorization: Bearer $CONSOLE_ACCESS_TOKEN" \
  -F file=@users.csv \
  -F passwords=import \
  -F keep_social=true \
  -F keep_verified=true \
  -F keep_mfa=true \
  -F keep_ids=true
```

| Feld | Bedeutung |
| - | - |
| `file` | CSV oder JSON, UTF-8 (eine Byte-Order-Mark ist zulässig), bis zu 25 MB. |
| `passwords` | `import` (Standard) übernimmt die Passwort-Hashes. `reset` ignoriert sie, sodass alle ein neues Passwort festlegen. |
| `keep_social` | Social-Login-Verknüpfungen beibehalten. |
| `keep_verified` | `email_verified` beibehalten. Bei `false` gilt jede E-Mail-Adresse als nicht verifiziert. |
| `keep_mfa` | Geheimnisse der Authenticator-App beibehalten. |
| `keep_ids` | `external_id` beibehalten. Bei `false` wird `vendor_data` zur Didit-Identitäts-ID. |
| `firebase_signer_key`, `firebase_salt_separator`, `firebase_rounds`, `firebase_mem_cost` | Die Hash-Parameter Ihres Firebase-Projekts. Senden Sie alle vier oder keinen. |

Jeder Schalter `keep_*` ist `true`, sofern Sie nicht `false` senden. Der Aufruf gibt `202` mit einem Auftrag zurück. Eine Anwendung speichert gleichzeitig höchstens 5 hochgeladene Dateien.

### 2. Probelauf prüfen

Jeder Upload startet einen Probelauf. Dieser prüft jede Person und schreibt nichts. Fragen Sie `GET import-jobs/{id}/` ab:

| Feld | Bedeutung |
| - | - |
| `kind` | `dry_run`, danach `import`, sobald Sie den Import starten. |
| `status` | `queued`, `running`, danach `passed` oder `failed` für einen Probelauf, `done` oder `error` für einen Import. |
| `processed`, `total` | Fortschritt in Personen. |
| `result` | Inhalt der Datei (siehe [Ergebnisse](#ergebnisse)). |
| `startable` | `true`, wenn der Probelauf bestanden wurde und die Datei nicht abgelaufen ist. |
| `expires_at` | Die Datei wird 24 Stunden nach einem Probelauf gelöscht, der nie importiert wurde. |

Ein fehlgeschlagener Probelauf listet seine Fehler auf. Korrigieren Sie die Datei und laden Sie sie erneut hoch.

### 3. Import starten

`POST import-jobs/{id}/start/` importiert, was der Probelauf geprüft hat, und gibt `202` zurück.

* Personen werden in Batches von 1.000 importiert. Jeder Batch wird einzeln übernommen.
* Pro Anwendung läuft jeweils nur ein Import (sonst `import_running`).
* Ein unterbrochener Import wird nach seinem letzten übernommenen Batch fortgesetzt.
* Bereits importierte Personen werden übersprungen, sodass Sie dieselbe Datei erneut ausführen können.
* Die Person, die den Import gestartet hat, erhält eine E-Mail, wenn der Import endet oder abbricht.

Die hochgeladene Datei wird verschlüsselt gespeichert. Sie wird gelöscht, wenn der Import endet, wenn ihr Probelauf fehlschlägt oder 24 Stunden nach einem Probelauf, der nie importiert wurde.

## Importe in Batches

Senden Sie bis zu 1.000 Personen pro Anfrage mit einem `idempotency_key` von höchstens 100 Zeichen.

```json theme={null}
{
  "rows": [
    {
      "email": "user@example.com",
      "external_id": "user-1234",
      "email_verified": true,
      "display_name": "Example User",
      "password_hash": "$2b$12$...",
      "social": [{ "provider": "google", "subject": "1234567890" }],
      "organizations": [{ "slug": "acme", "name": "Acme", "role": "admin" }]
    }
  ],
  "dry_run": true,
  "idempotency_key": "migration-batch-0001"
}
```

* Ein Probelauf gibt `200` mit dem Ergebnis zurück und schreibt nichts.
* Bei `dry_run: false` gibt ein erfolgreicher Import `201` zurück. Ein Batch mit einem Fehler gibt `400` zurück und importiert niemanden.
* Eine Wiederholung einer übernommenen Anfrage mit demselben Schlüssel gibt das gespeicherte Ergebnis zurück. Derselbe Schlüssel mit anderem Inhalt gibt `400 {"idempotency_key": "different_payload"}` zurück.
* Firebase-Parameter gehören in ein `firebase`-Objekt: `{signer_key, salt_separator, rounds, mem_cost}`.
* `GET imports/` listet übernommene Batches auf.

## Dateiformat

### JSON

Ein Array mit Personen oder ein Objekt, dessen `users` das Array enthält.

| Feld | Regel |
| - | - |
| `email` | Pflichtfeld. |
| `external_id` | Bis zu 255 Zeichen. Wird zum [`vendor_data`](https://docs.didit.me/de/entities/vendor-data-linking) der Person. |
| `email_verified` | `true` oder `false` (auch `1`/`0`, `yes`/`no`). |
| `display_name` | Bis zu 200 Zeichen. |
| `password_hash`, `password_salt`, `password_algorithm` | Siehe [Passwörter](#passwörter). |
| `totp_secret` | Base32-Geheimnis der Authenticator-App. |
| `created_at`, `last_signed_in_at` | ISO 8601. Eine Zeitangabe ohne Zone gilt als UTC. |
| `social` | Bis zu 8 `{provider, subject, tenant_id}`. `provider` ist `google`, `apple`, `github` oder `microsoft`. `tenant_id` ist nur bei Microsoft erforderlich. |
| `organizations` | Bis zu 20 `{slug, name, role}`. `role` ist `admin`, `member` (Standard) oder `billing`. |

### CSV

Dieselben Spalten mit einzelnen Werten, dazu `google_id`, `apple_id`, `github_id`, `microsoft_id`, `microsoft_tenant_id`, `organization_slug`, `organization_name` und `organization_role`. Eine Person in mehreren Organisationen bekommt eine Zeile pro Organisation.

```csv theme={null}
email,external_id,email_verified,password_hash,google_id,organization_slug,organization_role
user@example.com,user-1234,true,$2b$12$...,1234567890,acme,admin
```

Unbekannte Felder oder Spalten sind Fehler und werden niemals ignoriert.

### Duplikate und bestehende Konten

* Zeilen mit derselben E-Mail-Adresse ergeben eine Person. Eine identische Zeile zählt als Duplikat. Eine Zeile mit mehr Verknüpfungen, Organisationen oder einer Authenticator-App wird zusammengeführt.
* Zwei verschiedene Passwort-Hashes, Authenticator-Geheimnisse oder Organisationsrollen für dieselbe Person sind Fehler. Ebenso ist es ein Fehler, wenn zwei Personen dieselbe `external_id` oder denselben Provider-Subject beanspruchen.
* Existiert bereits ein Konto mit dieser E-Mail-Adresse und hat ein früherer Import es erstellt, wird die Person übersprungen (`already_imported`). Hat sich die Person selbst registriert, ist es ein `identity_conflict`-Fehler. Ein Import ändert niemals ein bestehendes Konto.

## Passwörter

| Format | Akzeptiert |
| - | - |
| bcrypt | `$2a$`, `$2b$` oder `$2y$`, Kostenfaktor 4 bis 13. |
| Argon2id | `$argon2id$v=19$...`. Bis zu 64 MiB Speicher, Speicher mal Durchläufe bis zu 256 MiB, bis zu 8 Lanes. |
| Django PBKDF2-SHA256 | `pbkdf2_sha256$...`, 10.000 bis 2.000.000 Iterationen. |
| Django scrypt | `scrypt$...`, Arbeitsfaktor bis 32.768, Blockgröße 1 bis 8, Parallelität 1 bis 5. |
| Firebase scrypt | `password_algorithm: "firebase_scrypt"`, base64-codiertes `password_hash` und `password_salt` pro Person sowie Ihr `signer_key`, `salt_separator`, `rounds` (1 bis 8) und `mem_cost` (1 bis 14), einmal pro Import. |

Firebase-Parameter werden verschlüsselt und nur für diese Anwendung gespeichert. Jedes andere Format oder ein Kostenfaktor über diesen Grenzwerten bedeutet, dass die Person ein neues Passwort benötigt. Nach der ersten erfolgreichen Anmeldung einer Person speichert Didit ihr Passwort erneut in dem eigenen Format.

### Personen ohne verwendbares Passwort

Wenn für eine Person kein Hash oder ein nicht unterstützter Hash vorliegt, erhält ein Anmeldeversuch mit Passwort die übliche Antwort für ein falsches Passwort. Didit sendet dieser Person außerdem einen Link per E-Mail, um ein neues Passwort festzulegen (30 Minuten gültig, höchstens einmal täglich). Das geschieht nur, wenn ihre E-Mail-Adresse verifiziert ist, das Konto nicht gesperrt ist und die Anmeldung mit Passwort aktiviert ist.

Im Ergebnis zählt `reset_required` die Personen ohne verwendbares Passwort. `reset_emails` zählt davon diejenigen mit einer verifizierten E-Mail-Adresse, denn nur sie können auf diesem Weg erreicht werden. Personen mit einer nicht verifizierten E-Mail-Adresse können sich nicht mit einem Passwort anmelden. Sie können sich weiterhin mit einem E-Mail-Code anmelden, sofern Sie diese Methode aktivieren.

## Social-Links, Authenticator-Apps und Organisationen

* **Social-Links** werden für Anbieter beibehalten, die Ihre Anwendung bereits konfiguriert hat, unter der aktuellen Client-ID Ihrer Anwendung. Bei einer erneuten Anmeldung ist dann kein Nachweis per E-Mail erforderlich. Google- und GitHub-Subjects sind global. Apple-Subjects sind pro Entwicklerteam und Microsoft-Subjects sind pro App-Registrierung gültig, daher passen diese Links nur, wenn Ihre alte Plattform dasselbe Team oder dieselbe Registrierung verwendet hat. Links zu Anbietern, die Sie nicht konfiguriert haben, werden als `social_unavailable` gezählt und nicht importiert.
* Die TOTP-Secrets der **Authenticator-Apps** (SHA-1, 6 Stellen, 30 Sekunden) werden verschlüsselt gespeichert. Importierte Personen müssen sie als zweiten Faktor verwenden. Sie erhalten keine Wiederherstellungscodes.
* **Organisationen**, die noch nicht existieren, werden anhand ihres Slugs angelegt.
* **Passkeys** können nicht importiert werden. Sie gehören zu Ihrer alten Anmelde-Domain, daher fügen Personen nach der Anmeldung bei Didit einen neuen Passkey hinzu.

## Ergebnisse

Jeder Probelauf, jeder Batch und jeder Job liefert dasselbe `result`:

| Feld | Bedeutung |
| - | - |
| `rows`, `people_total` | Gelesene Zeilen und eindeutige Personen. |
| `ready`, `already_imported`, `duplicates`, `merged`, `skipped` | Was mit jeder Person geschieht. |
| `passwords` | Anzahl pro Format: `bcrypt`, `argon2id`, `firebase_scrypt`, `pbkdf2_sha256`, `scrypt`. |
| `reset_required`, `reset_emails`, `unsupported_hashes` | Personen, die ein neues Passwort benötigen. |
| `verified_emails` | Personen, die mit verifizierter E-Mail-Adresse importiert wurden. |
| `social_links`, `social_unavailable` | Pro Anbieter beibehaltene Links und übersprungene Links. |
| `authenticator_apps` | Personen mit einer importierten Authenticator-App. |
| `organizations` | `count`, `members` und bis zu fünf `names`. |
| `error_count`, `errors` | Bis zu 100 Fehler, jeweils mit einer nullbasierten `row` (CSV-Zeile `row + 2`), einem `field` und einem `error`-Code. |

Antworten enthalten niemals Passwort-Hashes, Salts, Authenticator-Secrets oder Feldwerte.

| Fehler | Bedeutung |
| - | - |
| `file_too_large`, `too_many_rows`, `empty_file` | Über 25 MB, über 100.000 Personen oder leer. |
| `invalid_encoding`, `invalid_json`, `invalid_csv` | Die Datei kann nicht gelesen werden. |
| `unknown_column`, `missing_column`, `duplicate_column`, `unknown_field` | Die Datei entspricht nicht dem Format. |
| `required`, `invalid_field`, `invalid_row` | Ein Wert fehlt oder ist ungültig. |
| `identity_conflict` | Ein Konto mit dieser E-Mail-Adresse wurde selbstständig registriert. |
| `social_link_conflict`, `password_conflict`, `authenticator_conflict`, `role_conflict` | Zwei Zeilen zu einer Person widersprechen sich. |
| `too_many_uploads`, `import_running`, `not_startable`, `file_expired` | Der Job kann derzeit nicht starten. |

## Vor der Umstellung

Testen Sie in Ihrer Sandbox-Anwendung:

* die Anmeldung mit jedem importierten Passwortformat,
* die E-Mail zum Festlegen eines neuen Passworts für eine Person ohne verwendbaren Hash,
* Social-Anmeldung und Authenticator-Codes für importierte Personen,
* die zweimalige Ausführung derselben Datei.

Prüfen Sie anschließend, ob das `vendor_data` jeder Person mit dem Wert übereinstimmt, den Sie für deren [Verifizierungen und Transaktionen](https://docs.didit.me/de/authentication/overview.md#dieselbe-person-über-alle-produkte-hinweg-verwenden) verwenden.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.