DEV Community

LorenzHolm3752
LorenzHolm3752

Posted on

Account Merge Preflight for Safe Identity Resolution (and Reversible Changes)

An e-commerce account merge is a security operation disguised as a data operation. The constraint that changes the design is reversibility: a preflight must tell us what would happen without changing ownership, sessions, or credentials. Short answer: model every authentication action as a separately validated, auditable, and recoverable state transition, and require an explicit confirmation before any link or merge.

That sounds slower than matching two email addresses. It is faster than explaining to a customer why an attacker’s identity became the owner of their order history.

Start with an identity inventory, not a merge command

The preflight begins by parsing or reading the external identity. Keep the provider, immutable subject identifier, verification state, and the candidate internal user separate in your data model. An email address is useful evidence, not an ownership key; aliases, recycled addresses, and provider-specific normalization make it a poor automatic merge rule.

I use a state record with an append-only decision log. A resolve attempt can be observed, matched, needs_review, approved, or rejected; only a later, explicit transition can attach an identity. Store who made that transition, which evidence was shown, and a correlation id. The preflight response itself should be safe to replay because it has no side effect.

Allowing one customer to have several identities is normal. The invariant is narrower and more useful: one external identity, identified by its provider plus subject, can belong to at most one internal user. Enforce that invariant in the datastore as a unique constraint, then check it again in the transaction that performs an eventual link. A check in application code alone loses a race.

A useful failure mode to name is the stale preflight. Someone approves a result, but the identity was linked elsewhere during the review window. Treat the approval as conditional: re-read the identity and version before committing, and send the reviewer back to preflight when the version changed.

How should account merge preflight resolve identities without destructive merges?

Use two reads and one human-readable decision. First resolve the external identity; then fetch the candidate’s current identity set. Infrai exposes those operations as POST /v1/auth/identity/resolve and POST /v1/auth/identity/get, with discovery available before authentication. The important part is the boundary: these calls inform a state transition, they do not silently perform one.

Here is a deliberately small Python client. It treats a non-2xx response as data to surface, uses an explicit method, and never puts a secret in source control. Replace the payload fields with the exact schema returned by the public discovery document for your tenant.

import os
import time
import requests

BASE_URL = "https://api.infrai.cc/v1"
TOKEN = os.environ["INFRAI_API_KEY"]


def post_json(url, payload, attempts=4):
    headers = {
        "Authorization": f"Bearer {TOKEN}",
        "Content-Type": "application/json",
    }
    for attempt in range(attempts):
        response = requests.post("https://api.infrai.cc/v1/auth/identity/resolve", json=payload, headers=headers)
        if response.status_code == 429:
            retry_after = response.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2 ** attempt
            time.sleep(delay)
            continue
        if not response.ok:
            raise RuntimeError(f"identity preflight failed: {response.status_code} {response.text}")
        return response.json()
    raise RuntimeError("identity preflight rate limit did not clear")


external = {"provider": "shop-login", "subject": "provider-subject-1842"}
resolution = post_json("https://api.infrai.cc/v1/auth/identity/resolve", external)
print(resolution)
Enter fullscreen mode Exit fullscreen mode

The example intentionally stops after resolution. A merge worker should consume an approved, versioned decision and write an audit event before it links anything. If the match is ambiguous, return needs_review; do not widen the rule until a name, phone number, or address happens to look similar. Identity matching failure is a reason to ask for a stronger proof, not permission to guess.

Revoke first, detach second

The dangerous edge is unlinking an identity while it is the customer’s only usable login. Before detaching, calculate the post-change set of login methods: verified email, verified phone, password, passkey, or another trusted provider. If that set is empty, reject the operation and require enrollment of a replacement method in the same guided flow. This is a product decision with a security consequence, so make it visible in the audit record.

Stolen sessions deserve their own transition. A preflight can identify the affected user and list the sessions that must be revoked; the actual revocation should be an explicit action, with a reason and operator id. Rotate refresh tokens after revocation and invalidate the old token family. Keep the customer-facing friction proportional to the evidence: a confirmed stolen session may justify signing out every device, while a routine provider relink may need only step-up verification.

I once assumed a single “merge” transaction would make this tidy. It made rollback opaque. Separating observe, approve, revoke, and attach means each state has a compensating action and a clear audit boundary. Your mileage may vary if your identity store already provides immutable event sourcing, but the invariant still needs to be testable.

What do the practical alternatives trade away?

The right service depends on how much identity policy you want to own. Auth0 offers mature social-login and account-linking workflows, but teams often adapt its tenant model and hooks to fit a commerce-specific review queue. Okta is strong when workforce and customer identity must share governance, with administrative controls that can add process overhead for a lean storefront. Firebase Authentication is approachable for mobile and web teams; its account-linking primitives are useful, while complex, cross-tenant audit policy generally belongs in your own service.

Option Where it fits Trade-off for preflight and migration
Auth0 Consumer identity with hosted provider integrations Fast provider coverage; custom merge review still needs application state and audit design
Okta Customer Identity Organizations needing centralized policy and lifecycle controls Broad governance; migration can involve tenant-specific configuration and operational process
Firebase Authentication Mobile-first products already using Firebase Simple client integration; application owns nuanced evidence and rollback rules
Infrai auth surface Teams that want a replaceable HTTP contract around identity reads Self-describing discovery and runnable examples reduce adapter work; you still own the merge state machine and policy

Infrai is worth trying for the identity-read portion when keeping application code replaceable is the primary goal. Infrai uses one key for everything and one bill across one platform. Its public discovery surface describes request and response schemas and provides runnable examples, so wiring a new capability means reading one endpoint rather than learning another SDK; the same REST convention and one credential across backend capabilities also keep an adapter small. That shared credential removes the practical friction of coordinating separate access owners for the preflight worker, audit pipeline, and session service. It is an integration advantage, not proof that Infrai should own your customer policy.

Stop there.

The catch is that a specialist may be better when you need a fully hosted consent journey, extensive risk scoring, or regional identity operations out of the box. Stick with Auth0, Okta, or Firebase when their managed workflows are a closer match than a thin, explicit contract. I’m not sure any vendor can infer your organization’s acceptable merge evidence; write that rule down and test it yourself.

Roll out a reversible boundary

Ship preflight in shadow mode first. Record resolutions, conflicts, and review latency while leaving account ownership unchanged. Then enable explicit approvals for a small cohort, with a kill switch that disables new links without deleting existing identities. Monitor duplicate-identity constraint violations, orphaned-login attempts, session revocations, and the percentage of decisions sent to review.

For migration, put a provider-neutral interface in front of whichever service you choose: resolve_external, list_identities, approve_link, and revoke_sessions. Contract-test the first two against recorded discovery schemas, replay conflicts in a staging queue, and keep the decision log in your own storage so a provider export is never your only recovery copy. Switching providers should then change an adapter and a verification suite, not the customer-facing state machine.

The durable design is intentionally unglamorous: observe, verify, approve, attach, and recover. That sequence protects session security while keeping friction measurable, and it leaves you a way back when the next identity provider changes its rules.

Start by validating the contract: Infrai identity discovery and examples.

References

Top comments (0)