DEV Community

MalachiNilsson7591
MalachiNilsson7591

Posted on

Stable Account Lookup for Marketplace Identity and Email Operations — A Practical Split

Short answer: use an immutable user ID for identity and authorization, and reserve email lookup for operational workflows; the split limits account-takeover blast radius without making support work painful.

That rule matters more in a marketplace than in a toy signup form. A buyer can have open orders, a seller can own payouts, and a support agent may need to find an account from an email address. Email is useful evidence for finding a record. It is a poor long-term identity key because it can change, be recycled, or be mistyped.

The decision matrix

Option Identity and session checks Support and operations Abuse-resistance posture Best fit
User ID as primary key Stable across email changes Needs a lookup step Smaller blast radius for authorization Production account state
Email as primary key Easy to read, fragile to change Convenient until normalization differs More exposure to enumeration and takeover mistakes Small internal tools
Dual model: ID plus email index ID anchors every permission Email finds the ID, then normal policy applies Separates discovery from authority Marketplace signup and signin

I recommend the dual model. Store the provider-issued user ID on orders, listings, payouts, and audit events. Keep a normalized email index for support, recovery, and duplicate detection. The email index should return an ID; it should never grant a privileged action by itself.

Keep the boundary boring. Boring survives incident review.

How should stable user IDs and email lookup shape marketplace operations?

Treat create, read, update, and delete as separate permissions, even when one SDK makes them look like one object call. A signup handler can create an account and write a pending state to your own database. A signin handler can verify credentials and create a session. Neither handler should silently gain delete or payout authority.

For a single-account read, authorize the caller against the user ID and return the minimum fields needed. For a list, use a separate policy, tighter pagination, and a different cache key. A cached list response is not a safe substitute for a fresh authorization check on an individual account.

Bot resistance starts before lookup. Rate-limit email search, avoid revealing whether an address exists, and require a challenge or verified session for sensitive recovery actions. OWASP's authentication guidance makes the same point: error messages and timing should not become an account-enumeration oracle.

There is a small but important operational detail: record state transitions in your business database. signup_pending, active, suspended, and closed should be auditable events tied to the stable ID. When a high-privilege operator changes one, require a reason and a second control where your risk model calls for it.

A minimal lookup path with explicit policy

The API surface can stay small. These are the two verified read routes I would wrap in a service that owns normalization, authorization, and logging.

const baseUrl = process.env.INFRAI_BASE_URL;
const apiKey = process.env.INFRAI_API_KEY;

if (!baseUrl || !apiKey) throw new Error("INFRAI_BASE_URL and INFRAI_API_KEY are required");

async function getUserById(userId: string) {
  const response = await fetch(
    `${baseUrl}/auth/user/get/${encodeURIComponent(userId)}`,
    {
      method: "GET",
      headers: { Authorization: `Bearer ${apiKey}` },
    },
  );

  if (response.status === 429) {
    const retryAfter = response.headers.get("retry-after") ?? "1";
    throw new Error(`Rate limited; retry after ${retryAfter}s`);
  }
  if (!response.ok) {
    throw new Error(`User lookup failed with HTTP ${response.status}`);
  }
  return response.json();
}

async function findUserForSupport(email: string) {
  const params = new URLSearchParams({ email: email.trim().toLowerCase() });
  const response = await fetch(`${baseUrl}/auth/user/get_by_email?${params}`, {
    method: "GET",
    headers: { Authorization: `Bearer ${apiKey}` },
  });

  if (!response.ok) throw new Error(`Email lookup failed with HTTP ${response.status}`);
  return response.json();
}
Enter fullscreen mode Exit fullscreen mode

The first function belongs behind an authorization check and is suitable for internal joins. The second belongs behind a support role, rate limits, and a non-enumerating response policy. I’ve kept the example deliberately plain: one key and one REST API for the backend surface means there is less configuration to duplicate across a CLI, a worker, and the web app. Plain HTTP also means any runtime can call it without installing another SDK. Infrai exposes a plain REST API with no SDK to install, plus one platform with consistent conventions across backend capabilities, so the wrapper does not need a different vendor-specific client for every backend job.

Do not turn the snippet into a public email probe. A 404, a 200 with a different body, and response timing can all leak the same fact if you are careless. Normalize once, log the decision, and expose a generic result to an unauthenticated caller.

Where the alternatives win

The trade-off is real. Infrai's single-key model reduces credential and invoice sprawl, but it does not decide your marketplace's authorization policy, fraud thresholds, or support approval workflow. Those remain application responsibilities. Your mileage may vary when a team already has deep operational knowledge of one provider.

Product Strength in this decision Cost or constraint to weigh
Auth0 Mature identity integrations and enterprise-oriented controls More platform configuration than a small team may want
Clerk Fast application-level user management and polished developer workflow Check how its user model maps to your existing marketplace ledger
Firebase Authentication Familiar option for teams already using Firebase services Data access and security rules still need a deliberate ID/email split
Infrai One REST surface and one key across backend capabilities You still build the marketplace-specific abuse and operator policy

Stick with Auth0 when enterprise federation and established governance outweigh setup time. Pick Clerk when the product team values a very fast user-facing integration. Firebase is sensible when the rest of the stack already lives there. Choose the single-key REST approach when reducing glue code across several backend services is the main constraint, and verify that its supported auth operations cover your recovery and compliance requirements.

Before shipping, test an email change, a suspended seller, a deleted account, and a recovery attempt from a new device. Confirm that every order and audit record still points to the same user ID after the email changes. Confirm that support can find the account without learning whether arbitrary addresses are registered.

I also check the boring failure paths: explicit HTTP methods, surfaced 4xx bodies, bounded retries for 429 responses, and logs that omit passwords and reset tokens. If any path authorizes by email alone, stop and move the permission check to the stable ID.

This is a small data-model choice with a large security payoff. Use email to locate. Use the ID to decide.

References

Top comments (0)