stable · guide · 0.1.0
Hosted Identity Migration
The Project Backend owns recent legacy authentication and the durable mapping from its stable legacy subject to a MiniCenter Account ID. MiniCenter owns hosted registration, explicit Platform Account authentication, fresh email verification, and the account-linking step. Passwords, factors, roles, content, and project business data never cross this boundary.
1. Create the transaction server-to-server
After recent legacy authentication, follow the complete Create Identity Migration Transaction contract with an identity-migrations:write Project Credential and a stable Idempotency-Key. Its request schema, 201 response, 401/403/422 failures and normalized error envelope are also available in OpenAPI JSON. Set expires_at after the current time and no more than 30 minutes ahead. Persist the transaction ID, legacy subject, expiry, and opaque project state before continuing.
The creation response contains a one-time flow_token. Keep it only in the Project Backend; never put it in HTML, a URL, browser storage, logs, analytics, telemetry, email, or a third-party origin.
2. Exchange the flow token for a browser handoff
From the Project Backend, follow the Create Hosted Identity Migration Continuation contract with a new stable Idempotency-Key:
{
"flow_token": "CREATION_ONLY_FLOW_TOKEN",
"return_uri": "https://project.example.test/migration/callback",
"state": "OPAQUE_PROJECT_STATE"
}
return_uri must exactly match a redirect registered on the transaction's Project Client and must not contain a fragment. An uncertain response is recovered by repeating the identical request with the same idempotency key. The 201 response contains only id, continuation_token, and the authoritative expires_at; it never echoes the flow token. The handoff expires at the original transaction expiry (no more than 30 minutes after transaction creation), and neither expiry can be extended.
3. Submit the one-browser handoff
Render an auto-submitting form whose action is the MiniCenter origin plus /identity-migration/continue. Submit only these creation-response fields in the body:
This cross-origin handoff does not use the Project Backend's CSRF cookie. Its high-entropy continuation_token is the explicit, single-use handoff protection: MiniCenter validates and consumes it before binding the continuation to the receiving browser session. All later same-origin hosted forms use MiniCenter's normal CSRF token.
<form method="post" action="https://sandbox.minicenter.otus.tw/identity-migration/continue">
<input type="hidden" name="continuation_id" value="CREATION_RESPONSE_ID">
<input type="hidden" name="continuation_token" value="CREATION_RESPONSE_TOKEN">
<button type="submit">Continue securely to MiniCenter</button>
</form>
Do not add either value to the action URL. MiniCenter claims the handoff for one browser session, stores the original flow token only in encrypted server-side state, and rejects another browser, expiry, replay, or cross-client substitution.
4. Complete the hosted account flow
MiniCenter requires an existing Platform Account to authenticate explicitly for this transaction. A prior SameSite Platform Session does not skip that step. The account must then complete transaction-scoped fresh email verification; email equality never links accounts automatically. A new Platform Account instead completes the existing hosted registration, legal declarations, and fresh registration email verification before linking.
The user may cancel. Completion, cancellation, or expiry destroys the stored flow token and project state for that hosted continuation. Successful linking returns to the allowlisted return_uri with only state=OPAQUE_PROJECT_STATE; cancellation returns with state=OPAQUE_PROJECT_STATE&error=access_denied. Expired, invalid, replayed, or cross-browser handoffs fail on MiniCenter with 410 or a validation error and do not redirect, so an untrusted request cannot select a return target. The Project Backend recovers those cases by inspecting the transaction and starts a new recently authenticated transaction when required.
5. Recover and confirm in the Project Backend
Treat the browser return as a notification, not proof of identity. Resolve state to the locally stored transaction, then inspect GET /api/v1/identity-migration-transactions/{transactionId} with the Project Credential. This inspection is the lost response recovery path for a lost browser redirect or uncertain linking response.
Only when the response is link_pending, matches the expected Project Client and legacy subject, and contains the Account ID should the Project Backend write its unique Account ID mapping and mark the legacy subject migrated in one local database transaction. After that durable commit, call POST /api/v1/identity-migration-transactions/{transactionId}/confirm-link. Confirmation is safe to retry. Expired or cancelled attempts require recent legacy authentication and a new transaction; never reuse either token.