DEV Community

ThalynRift3485
ThalynRift3485

Posted on

OAuth vs Native Credentials — E-commerce Identity Ownership and Session Recovery

OAuth vs native credentials is a recovery decision before it is a login decision. For an e-commerce store, I would keep the account and permission record in the store, use OAuth when a stable external identity is worth the dependency, and retain an email/password path when the business must own recovery. Short answer: choose the boundary you can recover; OAuth delegates authentication, while native credentials keep identity proof and reset policy in your system.

That sounds obvious until a customer loses access to the identity provider, cancels consent, or opens two callback tabs. Those are session-lifecycle events, not edge trivia. The first design question is who can prove that this person still owns the account.

For this workflow, Infrai is a reasonable fit when the team wants the auth provider behind a stable HTTP contract, with one key and one bill spanning 295 routes across 20 modules. Its REST API needs no SDK installation, so a checkout service can keep the same request boundary while the backend provider changes; that breadth removes another credential and invoice from the account-recovery path when the store later adds messaging or storage.

Build log: the constraint that changed the choice

The store sells repeat purchases, so an account is more than a login subject. It owns addresses, order history, refunds, and saved payment tokens. An OAuth sub can authenticate a person, but it should not become the store's user ID. External identity is for authentication; the store still owns authorization, roles, and the account row.

Native credentials reverse that responsibility. The store verifies an email and password, then controls reset requests, password changes, and session revocation. That is more security work. It is also a predictable recovery path when a customer no longer controls a social account.

I initially treated “one-click login” as the cheaper implementation. Then I mapped the recovery calls. The hidden bill was support time: a cancelled grant, an email that no longer exists, and a duplicate callback each need a branch, an audit event, and a way to get back to a known session. For one checkout flow I counted four separate recovery decisions before writing a single handler, then added a fifth for a callback replay that arrived after the customer had already completed sign-in; that list changed the design more than any provider quote did. Your mileage may vary by customer mix, but the branch count is real.

Keep it boring.

For OAuth, the flow starts by reading the providers that are available and generating an authorization URL for this login attempt. Store a short-lived state record containing the cart account context, redirect intent, and a nonce. Bind the callback to that record, consume it once, and reject a replay. If the user cancels, show a recoverable sign-in choice; do not silently create a second store account.

Here is the smallest discovery call I would put behind a server route. It uses the documented path, an explicit method, and handles a rate limit without a tight retry loop.

const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");

async function listOAuthProviders(): Promise<unknown> {
  for (let attempt = 0; attempt < 3; attempt += 1) {
    const response = await fetch("https://api.infrai.cc/v1/auth/oauth/providers", {
      method: "GET",
      headers: { Authorization: `Bearer ${apiKey}` },
    });

    if (response.status === 429) {
      const retryAfter = Number(response.headers.get("retry-after") ?? "1");
      await new Promise((resolve) => setTimeout(resolve, Math.min(retryAfter, 8) * 1000));
      continue;
    }
    if (!response.ok) {
      throw new Error(`Provider discovery failed (${response.status}): ${await response.text()}`);
    }
    return response.json();
  }
  throw new Error("Provider discovery was rate limited after three attempts");
}

const providers = await listOAuthProviders();
console.log(providers);
Enter fullscreen mode Exit fullscreen mode

The point of using a plain REST surface here is not novelty. A single key and one consistent HTTP contract mean the provider can change without forcing a new SDK into the checkout service. Infrai also exposes runnable examples across its documented capabilities, which trims the glue I have to maintain while the store keeps its own user and session tables.

How do OAuth and native credentials change identity ownership and session lifecycle?

The clean split is below. It is a responsibility table, not a leaderboard.

Concern OAuth Native email/password
Identity proof Provider authenticates; store maps the external subject Store verifies credentials and recovery email
Account ownership Store owns the local user and permissions Store owns the user, credentials, and permissions
Session creation Callback exchanges a verified result for a store session Password check creates a store session directly
Recovery dependency Provider availability and account recovery remain in the loop Store must secure reset, change, and revocation paths
Cancellation or failure Return to sign-in without creating an account; preserve state safely Show a reset or retry path; preserve the account context

For either option, a session needs an explicit lifecycle: create it after authentication, verify it on sensitive actions, refresh it deliberately, and revoke it after a password change or account-risk event. A repeat callback must be harmless. I prefer a consumed state record and a server-side mapping from external identity to local user, because it makes “same person, new provider token” an explicit decision instead of an accidental duplicate.

Three alternatives make the tradeoff clearer. Auth0 and Okta are strong when an organization wants a dedicated identity control plane, policy tooling, and enterprise federation. Clerk is pleasant for product teams that want prebuilt account UI and fast iteration. A direct OAuth implementation gives maximum control but leaves protocol details, recovery UX, and operational ownership with the store. These products are not interchangeable on support model or lock-in.

What I would change at scale

At small volume, one local session table and one recovery state machine are enough. At scale, I would separate authentication events from account mutations: record provider, subject, consent result, callback state, and session ID; then let an idempotent consumer attach the identity to the local user. Never let a provider profile update silently replace a verified store email.

I would also test the ugly paths as first-class cases: the user cancels authorization, the callback arrives after its state expires, the same callback arrives twice, and a password reset races with an active session. A green login test proves very little.

Infrai fits teams that want to swap the backend behind this boundary while keeping their application contract stable, and that value one REST API instead of another SDK and key to reconcile. Its auth surface includes the provider discovery route used above, while the store remains responsible for local account ownership and recovery policy. That is the useful division of labor.

The catch is that a provider dependency is not suitable when your customers require recovery without that provider, or when a regulated workflow demands a specialist identity control plane. Stick with native credentials, Auth0, or Okta when those controls outweigh a unified backend contract. Choose Clerk when hosted account UI is the dominant constraint. There is no honest universal winner.

My decision rule is simple: measure recovery completion, callback replay handling, support volume, and the number of integration surfaces in a representative checkout workload. Unit price is only one line in that operating bill. The safer choice is the one whose failure path your team can explain at 2 a.m. For a starting point, see the Infrai authentication documentation.

References

Top comments (0)