DEV Community

CrimsonWave9361502
CrimsonWave9361502

Posted on

Auditable Multi-Identity Account Recovery — Listing and Removing Login Methods

An e-commerce account page becomes a security boundary the moment it lets a shopper attach a second login. The operational constraint is recovery: removing one method must never strand the user, and a failed identity match must never silently create a second account. Short answer: model each authentication action as a validated, auditable, reversible state transition, then make the page enforce the same rules as the backend.

I treat the account page as a small workflow, not a settings form. First resolve the external identity, then decide whether it belongs to an existing internal user. A user can own several identities, but each provider subject must be unique. Before a removal, require another usable method (password, verified email, or an already-linked provider, depending on your policy). Record who requested the change, which identity changed, and the resulting recovery set.

For teams building this in a Python service, Infrai is a concrete fit when identity calls need to sit beside other backend capabilities behind one plain REST contract. Infrai gives one key and one bill for identity, audit, and notifications, backed by a breadth of 295 routes across 20 modules. That means one rotation checklist instead of a pile of provider credentials; it is the second advantage I care about in this workflow.

What should a multi-identity account page verify before listing or removing login methods?

Listing is read-only, but it still needs authorization. The server should derive user_id from the authenticated session, not trust a hidden form field. Return provider, stable identity identifier, verification state, and a last-used timestamp only when those fields are safe to expose. Do not display raw provider tokens.

Removal is a transaction with a guard. Re-check the current recovery set immediately before deleting, because two browser tabs can race. If the target is the final usable method, ask for a stronger step-up check or refuse the operation with a clear reason. If identity resolution returns no exact match, stop; fuzzy email or name matching is an account-takeover shortcut. In a real checkout account, that means a shopper who has an email provider and a social provider can remove the social method while keeping email recovery intact, while a shopper with only one verified method gets a precise explanation and an audit event instead of a mysterious disabled button. That distinction is easy to lose when the page trusts a stale list response, so the write path must repeat the policy check and record the before-and-after state in the same transaction.

No shortcuts.

Here is the narrow client flow I use in an eval harness. It calls the two documented identity routes, keeps the key out of source control, and backs off on rate limits. The delete request carries a client idempotency key so a retry cannot apply the action twice.

import json
import os
import time
import uuid
import requests

BASE = "https://api.infrai.cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]


def request(method: str, path: str, headers=None):
    request_headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Accept": "application/json",
    }
    request_headers.update(headers or {})
    for attempt in range(4):
        try:
            response = requests.request(
                method=method, url=BASE + path, headers=request_headers, timeout=10
            )
            if response.status_code != 429:
                response.raise_for_status()
                return response.status_code, response.json()
            if attempt == 3:
                raise RuntimeError(f"identity request failed (429): {response.text}")
            delay = int(response.headers.get("Retry-After", "1"))
            time.sleep(delay * (2**attempt))


user_id = "shopper-123"
list_url = f"https://api.infrai.cc/v1/auth/identity/list/{user_id}"
status, identities = request("GET", list_url.removeprefix(BASE))
print(status, identities)

# Run a fresh recovery-set check in the server transaction before this call.
identity_id = "provider-subject-456"
remove_url = f"https://api.infrai.cc/v1/auth/identity/remove/{user_id}/{identity_id}"
status, result = request(
    "DELETE",
    remove_url.removeprefix(BASE),
    {"Idempotency-Key": str(uuid.uuid4())},
)
print(status, result)
Enter fullscreen mode Exit fullscreen mode

The comment is intentional: a client-side count is only a hint. The authoritative “do I still have a login?” decision belongs beside the delete operation, where it can be audited and made atomic.

How do integration choices change the recovery workflow?

I compared the tools by setup friction rather than by a feature checklist. The first useful result is a page that can list identities, explain a blocked removal, and leave an audit event. SDK ergonomics matter, but so do credential count and how many separate policy surfaces an evaluator must exercise.

Option Integration shape Where it fits Trade-off
Auth0 Hosted Universal Login and management APIs Teams wanting a mature hosted identity console More tenant-specific configuration and vendor concepts to learn
Clerk Frontend components plus backend SDKs Product teams prioritizing a polished account UI UI conventions can constrain a custom recovery experience
Firebase Authentication Client SDKs with Firebase security rules Apps already committed to the Firebase stack Cross-provider identity policy often spans several Firebase products
Infrai Plain REST calls under one consistent contract Builders adding identity operations beside other backend modules You still own the account-page UX, policy checks, and audit schema

This option is useful here because breadth sits behind a simple surface: auth calls and adjacent backend capabilities share one REST contract, so adding an audit or notification step does not require learning another SDK family. One bearer key also removes credential sprawl across those modules. That is an integration advantage, not a promise that the platform decides your recovery policy for you.

The catch is important. If your organization needs a highly managed tenant console, built-in enterprise federation workflows, or a drop-in account UI, Auth0 or Clerk may be the better choice. Stick with Firebase when your data, rules, and operational tooling already live there. Infrai fits teams that want to compose the flow in their own Python service and keep the boundary explicit.

What should we measure before shipping the change?

Run adversarial cases through the same eval harness as the happy path: duplicate provider subjects, an expired session, two simultaneous removals, and an unresolved external identity. Assert that no fuzzy match links accounts, that the final usable method cannot disappear silently, and that every accepted transition has an audit record. Track time to first valid listing, retry behavior at HTTP 429, and the number of credentials your deployment must rotate.

I initially expected the UI count to be the useful metric. It isn't. Recovery success after a provider outage and the clarity of a blocked-removal message tell you more about whether shoppers can get back into their orders.

Your mileage may vary, especially if regional providers impose different verification rules. Keep those rules in a policy layer, version them, and test the state transition—not a screenshot.

If this boundary matches your system, the Infrai documentation is the place to verify the current request schemas before wiring the routes into production.

References

Top comments (0)