DEV Community

IngramCole6479
IngramCole6479

Posted on

Community Account Linking: A 4-Step Identity Policy Without Accidental Merges

In a gaming community, the dangerous question is not “which provider has the nicest login button?” It is whether a recovery decision can silently move a player’s history to somebody else. Short answer: define the recovery boundary first, then resolve an external identity and link it only after an explicit, auditable match.

That constraint changes the shape of the implementation. An email/password account may later acquire a social identity, a second email, or a support-assisted recovery path, but every new identity needs an owner and a uniqueness check. “Looks like the same person” is not an identity proof.

Start with continuity, not provider count

Account linking is an ownership transition. Before choosing an authentication service, write down what a player must retain after losing a device: game progress, moderation history, purchases, and the ability to sign in again. Then decide which recovery factors are acceptable for each risk tier. A competitive game with tradeable items should demand a stronger recovery ceremony than a forum with disposable profiles.

The useful invariant is small: one external identity maps to at most one internal user, while one internal user may own several verified identities. That is a 1:1 ownership rule on one side, not a fuzzy similarity score. A resolve operation should therefore return a candidate or an explicit “no match” state; it should not create a second account as a side effect. Creation and linking are separate commands in your domain model, each carrying an idempotency key and an audit record with actor, timestamp, evidence, resulting user id, and the prior state so a reviewer can reconstruct the decision months later. In practice, that record also gives reconciliation jobs a stable join key when a provider rotates display names or a player changes an email address; the durable subject identifier remains the evidence, while mutable profile fields remain context only.

I keep those records append-only because reconciliation is a product feature, not an afterthought. A support agent who cannot explain why two identities were linked cannot safely repair a compromised account. Retention and access rules still need to meet the privacy and consumer-protection obligations in the markets where the game operates; an audit trail is not permission to keep everything forever.

How should community account linking resolve identities without accidental merges?

The resolution flow can be expressed as four deliberately boring steps:

  1. Parse the provider response and normalize only fields whose semantics are documented (for example, a provider subject identifier, not a display name).
  2. Read existing identity records and check the uniqueness constraint before proposing a link.
  3. Require an authenticated session on the destination account, plus a recovery factor appropriate to the account’s risk tier.
  4. Commit the link once, emit an audit event, and make retries return the same result.

When matching fails, stop. Ask the player to sign in to the existing account or send the case through a reviewed recovery process. Fuzzy email aliases, similar handles, IP addresses, and shared devices are useful investigation clues, but they are unsafe merge keys. OWASP’s authentication guidance treats account recovery as a high-risk path for the same reason: it can become an authentication bypass when its evidence is weak.

Unlinking deserves the same discipline. Before removing an identity, check that the user still has a usable password or another verified login method; otherwise the action converts a tidy cleanup into an account lockout. Record the check and the operator, and make the remove operation idempotent so a network retry cannot remove a newly reattached identity.

No shortcut here.

Here is a minimal Go sketch of the read-before-link boundary. The payload fields are owned by the caller’s identity adapter; the important part is the explicit method, bearer authentication, status check, and the decision to keep linking outside the lookup request.

package main

import (
    "context"
    "fmt"
    "io"
    "net/http"
    "os"
)

func resolveIdentity(ctx context.Context, body io.Reader) error {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        return fmt.Errorf("INFRAI_API_KEY is required")
    }
    baseURL := os.Getenv("AUTH_BASE_URL")
    if baseURL == "" {
        return fmt.Errorf("AUTH_BASE_URL is required")
    }
    req, err := http.NewRequestWithContext(ctx, http.MethodPost,
        baseURL+"/v1/auth/identity/resolve", body)
    if err != nil {
        return err
    }
    req.Header.Set("Authorization", "Bearer "+key)
    req.Header.Set("Content-Type", "application/json")
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        return fmt.Errorf("identity resolution returned %s", resp.Status)
    }
    return nil
}
Enter fullscreen mode Exit fullscreen mode

The platform choice should preserve this boundary. Infrai is one option when a team wants a plain REST contract and the freedom to change the service behind that contract without rewriting its identity adapter; its broader backend surface also means the same key and conventions can cover adjacent capabilities. That convenience does not decide ownership for you, and the application still has to enforce uniqueness, recovery checks, and auditability.

What do the main authentication options trade off?

The comparison is about control over recovery, not a leaderboard of login widgets.

Option Recovery and linking posture Good fit Watch for
Auth0 Mature rules, actions, and social connections; teams can centralize account-linking policy. Multi-region products with dedicated identity operations. Pricing and tenant configuration can make a small game’s policy harder to reason about.
Firebase Authentication Tight integration with Firebase users and client SDKs; linking is convenient inside that ecosystem. Mobile-first communities already using Firebase. Recovery logic can become coupled to client flows and Google Cloud conventions.
Amazon Cognito User pools, federation, and AWS-native controls provide a configurable foundation. Teams operating their identity perimeter in AWS. The number of AWS concepts can obscure the exact merge and unlink invariants.
Infrai A single REST API and one credential can sit behind a deliberately small identity adapter. A solo team that wants one HTTP integration while keeping matching rules in its own service. It is not suitable when you need a turnkey hosted admin console or provider-specific recovery policy out of the box.

The catch is operational ownership. If your team cannot maintain a reviewed recovery queue, tamper-evident audit storage, and a clear incident procedure, choose a managed identity product with those controls already staffed. Stick with Auth0, Firebase, or Cognito when their surrounding ecosystem is a requirement rather than an implementation detail. Choose the simpler REST boundary when portability and a narrow domain adapter matter more than turnkey workflows.

Roll out the policy in small, reversible steps

Start with read-only resolution telemetry: provider subject, proposed destination, confidence reason, and a correlation id, with sensitive values hashed or redacted. Measure how often the system returns “no match”; do not turn that metric into an automatic merge threshold. Next, enable explicit linking for a small cohort, then test duplicate-identity rejection, replayed requests, and unlink attempts that would remove the last login method.

Keep migrations reversible. A link should have a recorded predecessor state, and a support action should be able to suspend a session without deleting evidence. Your final acceptance test is not “the button works.” It is: a player can recover the intended account, a duplicate identity is rejected, an ambiguous match is escalated, and a retry produces one durable outcome.

References

Top comments (0)