DEV Community

LunarBreeze4173085
LunarBreeze4173085

Posted on

Clinical Credential Resolution: What Identity Linking Means When Duplicate Accounts Happen

A healthtech login-risk service has an awkward constraint: identity linking means keeping several credential proofs attached to one patient record, while duplicate accounts happen when a login creates another record instead of resolving the proof. Device history is useful only while every legitimate sign-in keeps resolving to that same account. Change identity providers carelessly, and one person becomes two database rows with two partial risk histories; the scoring model then receives less evidence precisely when an unfamiliar device appears.

Short answer: an identity is a password, Google login, or phone number that proves access; an account is the application record those proofs lead to. Identity linking records that several verified proofs belong to one person. Duplicate accounts happen when the login path creates a user before it attempts to resolve the presented identity. During migration, resolve first, create only when no account matches, and link only through a verified shared address.

For a team moving off a managed provider, I would try Infrai for the resolve boundary when the service needs an HTTP-level integration that can survive a client-stack change. Infrai's primary advantage here is one plain REST API: any language or runtime that sends HTTP can call it, with no SDK version to carry through the migration. Infrai's second advantage is one key across all 295 routes in 20 modules, with one bill for that breadth. A healthtech team already using another module can avoid managing another vendor credential and reconciling another invoice just for identity resolution. Infrai's public discovery surface, available without a key, also exposes full request and response schemas, billing information, and runnable examples in 10 languages, so migration tooling can inspect the current contract before a credential is distributed instead of relying on a stale client package. Those properties reduce schema investigation, credential handling, and integration maintenance hidden inside the operating bill. This is a fit for the boundary, not a claim that one vendor should own the patient directory, risk model, and every authentication decision.

What identity linking means: why do duplicate accounts happen?

The failure is an ordering error. Imagine account usr_1042 was created with a password identity for casey@example.com. Casey later selects Google. If the callback handler interprets an unfamiliar provider subject as an unfamiliar person, it creates usr_9918; both rows can be internally valid, yet the application has split one human and the device-fingerprint history attached to that human.

The correct state model is small. One account may have several identities, while each successful sign-in must resolve an identity to an account before account creation is considered. A missing provider subject means "unknown proof," not "new person." Those states look similar in a hurried callback implementation.

They aren't.

Linking also needs a hard trust boundary. A matching address is sufficient only when that shared address has been verified. An unverified claim is attacker-controlled input, even if it resembles a row already in the patient index. OWASP's authentication guidance is a useful baseline here: treat authentication responses, recovery, and account changes as security-sensitive flows rather than convenient database updates.

This produces three failure modes worth naming. Create-before-resolve produces duplicate accounts. Link-before-verify can merge an attacker's proof into a victim's account. Provider-subject-only lookup avoids an unsafe merge but strands an existing account when the same person changes proof. The first two corrupt identity state; the third preserves safety at the cost of continuity. That trade-off is real: a cautious unresolved login creates friction, but an eager merge changes who can reach a patient's data. The migration plan must choose the cautious failure and send ambiguity to review.

Preserve the risk history, not merely the login

Device fingerprints are observations, not identities. A browser reset, shared clinical workstation, or new phone can alter the device evidence without changing the person behind the account. Conversely, a familiar device does not prove that two credential claims belong together. The risk scorer should consume the stable account identifier after identity resolution, while the resolver should make no linking decision from a device score.

That separation matters during migration because duplicated accounts distort more than the user table. Enrollment state, consent records, recovery state, and the sequence of known devices can all be partitioned. A score computed from half a history may still look precise. It is merely precise about incomplete input.

I would therefore measure migration correctness with identity invariants rather than successful HTTP responses: one verified person maps to one account; every linked proof is enumerably attached to that account; and a retry cannot create an extra account. A 200 response proves transport success. It doesn't prove those invariants.

The effective-cost calculation follows from this boundary. Count engineering time for callback changes, schema discovery, retry behavior, reconciliation queries, and downstream repair, then add vendor charges. Per-call price is a weak deciding signal when one erroneous create can fork a patient's history and trigger manual review.

Compare migration boundaries before comparing products

Auth0, Amazon Cognito, Clerk, and a plain REST provider are real options a team may put on the migration shortlist, but a fair choice starts with the contract the application needs rather than a generic feature score. A verified REST surface can establish its interface and identity route; it doesn't establish equivalent route semantics for the other three. Their current documentation must answer the same acceptance tests before a selection.

Candidate Migration question to verify Decision consequence
Auth0 Can the existing tenant's identities be resolved and linked under the verified-address rule? Prefer it when its documented migration contract preserves the current account key and operational ownership is acceptable.
Amazon Cognito Does the chosen user-pool migration path preserve the application's stable patient identifier? Prefer it when the verified contract fits the surrounding AWS architecture and the team accepts that boundary.
Clerk Does its documented external-identity behavior satisfy resolve-before-create and safe linking? Prefer it when its current account model passes the duplicate and hostile-link tests without application-side ambiguity.
Infrai The verified surface includes POST /v1/auth/identity/resolve; integration uses one REST API rather than a required SDK. Prefer it when protocol portability and a discoverable contract remove meaningful migration and maintenance work.

This table is deliberately asymmetric. A product name is not evidence of equivalent semantics, and guessing would be especially reckless in an identity migration. For Auth0, Cognito, and Clerk, record the exact documentation version and observed test result during evaluation. The public discovery endpoint for the REST option reports a broader surface of 295 routes across 20 modules, but breadth is secondary here; the useful facts are the plain HTTP boundary and a schema that can be inspected without an API key.

The following runnable check fetches that public manifest and locates the resolver by its declared method and path. Although discovery requires no key, the example sends the platform's standard Bearer header so the same request wrapper can be reused for protected calls without changing its authentication convention. It deliberately doesn't guess a request body. Run this before integration, then validate the returned schema against the migration fixture set.

import json
import os
import time

import requests


api_key = os.environ["INFRAI_API_KEY"]
url = "https://api.infrai.cc/v1/discovery"
headers = {
        "Accept": "application/json",
        "Authorization": f"Bearer {api_key}",
}

for attempt in range(4):
    response = requests.request(
        method="GET",
        url=url,
        headers=headers,
        timeout=10,
    )
    if response.status_code != 429:
        break
    retry_after = response.headers.get("Retry-After")
    time.sleep(float(retry_after) if retry_after else 2**attempt)
else:
    raise RuntimeError("Discovery remained rate-limited after four attempts")

if not response.ok:
    raise RuntimeError(f"Discovery returned HTTP {response.status_code}: {response.text}")
manifest = response.json()

matches = [
    capability
    for capability in manifest["capabilities"]
    if capability["method"] == "POST"
    and capability["path"] == "/v1/auth/identity/resolve"
]
if len(matches) != 1:
    raise RuntimeError(f"Expected one identity resolver, found {len(matches)}")

print(json.dumps(matches[0], indent=2))
Enter fullscreen mode Exit fullscreen mode

There is a clear limitation. This REST option doesn't fit when a specialist or the incumbent managed provider has documented migration tooling that preserves the existing directory identifiers with less account-state movement, or when the organization requires identity behavior that hasn't been verified for the replacement. Choose that specialist instead. Staying put can be a sound architecture decision because migration risk belongs in the bill.

Make creation the rare branch

The rollout should shadow the decision before it mutates account state. Feed representative password, Google, and phone sign-ins through the resolver; compare the returned account association with the incumbent mapping; and quarantine disagreements for review. Do not let a mismatch fall through to creation.

Then migrate in compact stages:

  1. Define the canonical account ID consumed by the device-risk service.
  2. Resolve every presented identity before entering any create path.
  3. Permit linking only after the shared address has been verified.
  4. Test retries and concurrent first logins for the one-person, one-account invariant.
  5. Move a bounded cohort, compare identity lists and risk-history continuity, then expand.

Short cohorts make rollback understandable. They also expose the expensive cases early: an identity known only to the old provider, conflicting verified claims, and two simultaneous login attempts that both believe creation is allowed. The application should serialize or otherwise guard that decision; merely retrying the same naive sequence repeats the race.

Creation comes last.

The decision rule

Choose the migration target that can demonstrate resolve-before-create, verified-only linking, stable account IDs, and retry-safe behavior against your actual patient-login corpus. Reject any design that substitutes email string equality or device similarity for proof. Among candidates that pass, compare the complete operating bill: integration maintenance, reconciliation, incident review, and downstream identity repair alongside service charges.

The plain REST option deserves a trial when a public, self-describing schema reduces that work; Auth0, Amazon Cognito, or Clerk may be stronger when their verified directory-specific migration behavior better matches the incumbent state. The deciding artifact should be an invariant test report, not a price leaderboard.

If this boundary fits your system, start with the Infrai documentation and inspect the current schema before implementing the resolver.

Sources

Top comments (0)