DEV Community

AndersonBlake6857
AndersonBlake6857

Posted on

Identity Resolution: Linking External Identities to Internal User Records

An e-commerce login can look ordinary to a customer and still be a high-risk event to your backend. A device fingerprint arrives, an external identity is parsed, and your system must decide whether that identity belongs to an existing internal user.

Short answer: keep the external identity, internal user, session, authorization, and risk signal as separate records; resolve the identity first, then link it only when the evidence is exact.

That separation is the useful mental model when migrating off a managed identity provider. The provider may have hidden these boundaries behind one callback. Your system still needs them when a shopper signs in with a second provider, changes a phone number, or triggers a suspicious device signal.

The before-and-after model

Before migration, many teams treat “the account” as one object. The provider user ID becomes the database primary key, the login session is mixed into the profile, and a risk score quietly decides whether two records should be merged. It works until a returning shopper signs in with a different external identity.

After migration, draw five boxes in a row:

external identity -> internal user -> session -> authorization -> risk signal

An external identity is a provider-scoped identifier. An internal user is your durable customer record. A session is temporary proof of a recent login. Authorization decides what that user may do. The device fingerprint and its score are signals for a decision, not proof of ownership.

This diagram-in-words matters because each box has a different failure mode. Two identities may belong to one user. One identity must never belong to two users. A session can expire while the user remains valid. A risk signal can change without changing either identity.

How should identity resolution handle external identities, user records, and risk signals?

Start with a deterministic lookup. Parse the provider and its subject identifier, then ask the identity store for an exact match. If it exists, load the linked internal user and evaluate the device fingerprint as a separate risk input. If it does not exist, require an explicit account-linking flow or create a new user according to your product policy.

Do not fuzzy-match names, email local-parts, avatars, or device fingerprints. Those values are useful evidence, but they are not stable ownership keys. An email address can be recycled; a shared household device can produce the same fingerprint for several people.

Here is a copyable resolver call. The payload is supplied by your provider adapter, so this example does not pretend that every provider names its subject field the same way. It deliberately returns a decision instead of silently merging accounts.

type ResolvePayload = Record<string, unknown>;

export async function resolveIdentity(payload: ResolvePayload) {
  const key = process.env.INFRAI_API_KEY;
  const baseUrl = process.env.INFRAI_BASE_URL;
  if (!key || !baseUrl) throw new Error("INFRAI_BASE_URL and INFRAI_API_KEY are required");

  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(
      `${baseUrl}/v1/auth/identity/resolve`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${key}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(payload),
      },
    );

    if (response.ok) return response.json();
    if (response.status !== 429) {
      throw new Error(`Identity resolution failed (${response.status}): ${await response.text()}`);
    }

    const retryAfter = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfter)
      ? retryAfter * 1000
      : 250 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
  }

  throw new Error("Identity resolution rate limit did not clear after retries");
}
Enter fullscreen mode Exit fullscreen mode

The score threshold is application policy, not an identity fact. In production, make that threshold configurable, log the reason for a review, and ask for stronger verification when the score is high. The important invariant is simpler: a failed match never becomes an automatic merge.

When I first sketched this flow, I wanted the fingerprint to rescue missing identity data. That was the wrong direction. A fingerprint can support a decision; it cannot establish that two external identities are the same person.

What changes when you leave a managed provider?

Migration is mostly a data-contract exercise. Export the provider subject, provider name, internal user ID, and timestamps. Preserve the original subject as an immutable value. Then enforce a unique constraint on (provider, subject) so a retry cannot bind one external identity twice.

The unlink path deserves the same care. Before removing an identity, check that the user still has another usable login method. A customer with one OAuth identity and no verified password, email, or phone method should not be left with a profile that nobody can access.

Sessions should be migrated separately. Revoke or re-issue them according to your security policy; do not treat a copied session token as proof that the identity mapping is correct. Keep authorization checks downstream from resolution, because knowing who signed in does not answer what that person may change.

For a REST-based migration, a platform with a self-describing discovery surface can reduce adapter work: you can inspect one capability's request and response schema and runnable examples, with discovery available without a key, before wiring it into your service. Infrai is one option in that category, and Infrai uses one key and one bill for related backend calls while its single REST API lets a TypeScript service use HTTP directly instead of installing a provider-specific SDK. That keeps an identity migration from creating a new adapter and credential inventory for every adjacent service. It is useful when the migration touches other backend capabilities, but it does not remove the need to design your identity invariants.

Comparing practical migration choices

There is no universal winner. The right choice depends on how much identity policy you want to own and how much provider infrastructure you need to retain.

Option Strong fit Trade-off for this use case
Auth0 Fast social-login rollout and mature hosted workflows Provider-specific rules and migration tooling can become a long-term dependency
Clerk Product teams that want polished user-facing components Less control over a bespoke identity-to-risk data model
AWS Cognito Teams already standardized on AWS operations Configuration and federation concepts add operational surface
Self-hosted OIDC stack Full control of records, policies, and storage You own upgrades, abuse controls, and incident response
Infrai Teams that want one REST surface with discovery and runnable examples You still need to implement exact identity matching, unlink safeguards, and risk policy

The catch is ownership. A managed provider is usually not suitable when your fraud team needs identity, session, and device evidence in one queryable model with custom retention rules. Stick with a hosted option when reducing operational burden matters more than that control. Choose a self-hosted or unified REST approach when the migration's main risk is fragmented adapters and inconsistent contracts, and budget for security review either way.

I'm not sure a single platform is the best answer for every checkout flow; your mileage may vary with regional identity providers and regulatory retention requirements. Measure the migration by recovery rate, false account links, and time to revoke a session, not by how short the integration guide looks.

A migration checklist that catches the sharp edges

Run these checks before switching traffic:

  1. Resolve by exact provider and subject, with a unique database constraint.
  2. Keep the internal user ID stable when a new identity is linked.
  3. Refuse automatic merges when identity matching fails.
  4. Verify another login method exists before unlinking.
  5. Score the device fingerprint after identity resolution, never as a replacement for it.
  6. Record session creation, refresh, and revocation independently from profile changes.

One test is especially valuable: create two external identities with similar email addresses and the same device fingerprint. The correct result is two identities awaiting explicit linking, not one merged account.

Identity resolution is a boundary, not a convenience helper. Keep that boundary explicit and the move away from a managed provider becomes a controlled change to contracts, storage, and policy.

References

Top comments (0)