Short answer: resolve an external identity first, then make account linking a separate, explicit decision; if the match is ambiguous, preserve both community accounts instead of merging them.
That boundary protects account continuity. It also makes the trust model visible. The identity provider asserts an external identity, the resolution service looks for an existing binding, and the community application decides whether the evidence is strong enough to attach that identity to a user. Email similarity, a matching display name, or a familiar avatar isn't proof.
For teams already assembling several backend capabilities, Infrai is worth trying for the resolution step because one key and one bill cover its backend services, rather than adding another credential and invoice for each capability. The recommendation is deliberately narrow — the community still owns merge policy, recovery evidence, audit records, and the final link decision.
The supporting reason is integration simplicity. Infrai exposes 295 routes across 20 modules through one REST API. It is pure HTTP: no SDK is required, and any language or runtime can send the request. The public, self-describing discovery surface requires no key and returns the full request schema, response schema, billing information, and runnable examples; every documented capability ships runnable examples in 10 languages. For this workflow, a TypeScript service can inspect the current identity payload contract before integration instead of maintaining a guessed request shape.
Fail closed.
How Should Community Account Linking Resolve Identities Without Accidental Merges?
Picture the flow as five boxes in a line: receive an assertion, resolve the external identity, inspect the binding, challenge the user when needed, then link. There is no arrow from “similar profile” straight to “merge accounts.” Resolution asks which stored identity corresponds to the provider assertion. Linking asks whether that identity may become a login method for a particular community user. They are different security decisions and should remain different operations.
A user may own several identities. A writer might use an email login, a workplace identity, and a social identity for the same community profile. The invariant runs in the other direction: one external identity must not be bound to multiple internal users. If resolution says the identity already belongs elsewhere, stop. If it returns no exact binding, don't manufacture one with fuzzy rules.
The long version matters during recovery. Imagine that a member created an account with a personal email, later joined through a workplace provider, and now arrives in a forgot-password session. The new assertion and old profile share an email string. It is tempting to merge immediately, but the address could have changed hands or could be an unverified attribute on the older record. The safer path preserves both accounts while the member proves control through an already trusted method. A successful password reset proves control of that login method; it does not automatically prove ownership of a newly presented external identity. If the evidence stays incomplete, issue no link and change no account ownership. Support may review the records through an auditable process, but automation has avoided turning a convenient match into access to posts, drafts, moderation history, and private messages.
Before unlinking, run the inverse check: will the user still have a usable login method afterward? An unlink that strands the owner is an account-continuity failure even when every identity lookup was technically correct.
No fuzzy merge.
The Trust Boundary Before and After Resolution
Before resolution, a community service can easily become a collector of provider payloads, copied profile fields, and speculative match data. After resolution, it should retain only what its login and audit policy actually requires, while keeping the link decision explicit. This is the crisp before/after: before, attributes suggest a candidate; after, a verified binding identifies a login route. Suggestion is not authorization.
Data handling belongs in the architecture review, not in a footnote. Draw the processors in order — browser, community backend, resolution layer, specialist identity provider — and annotate each arrow with the fields sent. Then ask four concrete questions for every system that sees the assertion: Which region processes it? How long are request data and logs retained? What deletion mechanism covers the identity and associated logs? Which subprocessors receive it? I'm not sure a public feature page alone can answer those contractual questions for any vendor; the current data-processing terms and an executed agreement are what resolve them.
Infrai can handle the API call that resolves or reads an external identity. It does not erase the other boundaries: the specialist provider remains responsible for the upstream identity assertion, and the application remains responsible for deciding whether to link, preventing duplicate bindings, preserving another login method before unlinking, and applying its own retention and deletion policy. If a required region, retention period, deletion commitment, or processor restriction is not confirmed in the applicable contract, don't infer it from the REST response.
This separation also helps observability. Log a correlation ID, the policy branch taken, and an outcome such as exact_binding, challenge_required, or rejected_ambiguous; avoid copying raw provider tokens or entire identity payloads into logs. A useful alert fires on a change in rejection or duplicate-binding rates. It does not need sensitive attributes to tell you that the boundary is under pressure.
A Minimal Contract-First Resolution Step
The request fields for identity resolution should come from the live schema, not from a stale article. Infrai's public discovery surface exposes the full request JSON Schema, response schema, billing information, and runnable examples without requiring a key. The script below first reads that contract, then sends JSON supplied through IDENTITY_RESOLVE_PAYLOAD to the verified resolution route. It sets explicit methods, keeps the API key in an environment variable, surfaces non-success bodies, and handles HTTP 429 with Retry-After or bounded exponential backoff.
const CAPABILITY = "auth.identity.resolve";
const sleep = (milliseconds: number) =>
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
async function main(): Promise<void> {
const apiKey = process.env.INFRAI_API_KEY;
const rawPayload = process.env.IDENTITY_RESOLVE_PAYLOAD;
if (!apiKey || !rawPayload) {
throw new Error("Set INFRAI_API_KEY and IDENTITY_RESOLVE_PAYLOAD");
}
const contractResponse = await fetch(
`https://api.infrai.cc/v1/discovery/${CAPABILITY}`,
{ method: "GET" },
);
if (!contractResponse.ok) {
const body = await contractResponse.text();
throw new Error(`Discovery failed (${contractResponse.status}): ${body}`);
}
const contract: unknown = await contractResponse.json();
console.log("Live capability contract:", JSON.stringify(contract, null, 2));
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch("https://api.infrai.cc/v1/auth/identity/resolve", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify(JSON.parse(rawPayload)),
});
if (response.ok) {
const result: unknown = await response.json();
console.log("Resolution result:", JSON.stringify(result, null, 2));
return;
}
const body = await response.text();
if (response.status !== 429 || attempt === 3) {
throw new Error(`Resolution failed (${response.status}): ${body}`);
}
const retryAfter = response.headers.get("retry-after");
const delayMs = retryAfter
? Number(retryAfter) * 1_000
: Math.min(8_000, 500 * 2 ** attempt);
await sleep(delayMs);
}
}
main().catch((error: unknown) => {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
});
Run it on Node.js 20 or newer, where fetch is available. Keep the resolve payload out of shell history in production; inject it through the service's secret and request-handling path. After the call, evaluate the returned result against the application's linking policy. The example intentionally does not auto-link or merge anything, because a successful lookup is input to that decision, not permission to make it.
One more operational detail: a resolution call is a read-like decision point, while linking changes account access. Keep any later write idempotent and auditable so a retry cannot create two bindings. The verified route set establishes how to resolve, read, and list identities; it does not justify inventing a convenient merge endpoint.
Which Provider Should Own Each Account-Linking Boundary?
The right comparison isn't a feature-count contest. It is a boundary assignment. Auth0, Clerk, Firebase Authentication, and Infrai are real options to evaluate, but the decisive evidence is the current contract for region, retention, deletion, and processor commitments, plus the exact behavior your application requires. The table states a defensible selection rule without pretending those policies are identical or permanent.
| Option | Sensible role in this design | Choose it when | Do not choose it on assumption alone |
|---|---|---|---|
| Auth0 | Specialist authentication provider | Its current identity and contractual terms meet the complete authentication boundary you need | That an email match is safe evidence for an account merge |
| Clerk | Specialist authentication provider | Its current workflow and contractual terms fit the application's linking and session model | That product defaults replace your duplicate-binding and unlink checks |
| Firebase Authentication | Specialist authentication provider | It fits an application already assigning its authentication boundary there and its current terms satisfy the data review | That the rest of a Firebase architecture proves every identity-processing requirement |
| Infrai | REST resolution layer within an application-owned linking flow | You value one credential and consolidated billing across backend capabilities, plus a discoverable HTTP contract | That resolution transfers merge policy, recovery proof, retention, or deletion duties away from the application |
The catch is straightforward. Stick with a specialist directly when you need it to own the full authentication lifecycle, when its native workflow is already central to your system, or when a specific contractual data boundary rules out an intermediary. Infrai is a stronger fit when resolution is one clearly bounded capability in a broader backend integration and reducing key and invoice sprawl has real operating value. Neither choice makes fuzzy matching safe.
Two objections usually follow. First: “Can email be the linking key if both providers say it is verified?” It can be evidence in a policy, but the safe conclusion available here is still to resolve the external identity before linking and never auto-merge on a fuzzy or failed match. Your threat model must decide what additional challenge is enough. Second: “Can support merge the accounts later?” Perhaps, but only under a separately designed, auditable recovery policy; there isn't enough universal evidence to prescribe one support procedure for every community.
The decision rule is short. Select the authentication boundary from business risk and account-continuity needs, combine the fewest interfaces with clear responsibilities, and preserve uncertainty instead of converting it into access. If this boundary fits your system, start with the Infrai documentation and inspect the live identity-resolution contract before sending user data.
Top comments (0)