The safest removal policy is driven by the recovery state that will exist after the change, not by how many login rows the account page shows. For a fintech account, remove a method immediately only when another verified, policy-eligible recovery path survives; otherwise stage the change or send it to staffed review. Account deletion is a separate terminal operation: revoke every session as part of the same workflow, then start the product's approved erasure process.
| Recovery state after the request | Pick this path | User-visible result | Operational signal |
|---|---|---|---|
| Another independent, verified path remains | Immediate removal | Method disappears after fresh proof | method_removal_allowed |
| A fallback exists but its ownership is stale or unclear | Staged removal | Confirmation is required before mutation | method_removal_pending |
| No usable recovery path remains | Block and offer enrollment | No method is removed | last_recovery_path_blocked |
| Account ownership is disputed | Staffed recovery review | Identity records stay unchanged | recovery_review_opened |
| The whole account is being deleted | Terminal deletion workflow | All sessions end; sign-in stops | account_deletion_committed |
This is the field guide. The table is intentionally about post-change state. A row count can't tell you whether the remaining email is verified, whether a passkey belongs to the user holding the session, or whether a recovery contact is still eligible under policy.
How should a multi-identity account page list login methods?
Build the list from the server's current identity records. Give every row an opaque method ID, a recognizable but redacted label, its type, verification state, last-used timestamp when available, and an action state such as removable, needs_confirmation, or blocked. The browser should never infer removability from the number of rendered rows. It doesn't have enough context, and an older tab can be looking at yesterday's state.
Keep authentication methods and recovery paths separate in the data model. They overlap, but they aren't interchangeable. A login method answers “can this principal authenticate?” A recovery path answers “can the rightful account holder regain access under this policy?” That distinction makes the UI honest: a method may appear in the list yet not count as the surviving recovery path needed to authorize removal.
The page should also explain a block without exposing hidden identity data. “Add and verify another recovery method first” is useful. “The account also has a phone ending in 0182” may disclose more than the current session is allowed to know.
Short labels. Server decisions.
Pick immediate removal when independent recovery survives
Immediate removal fits the ordinary case: the user has recently reauthenticated, the target method still belongs to this account, and at least one independent verified recovery path will remain after the write. OWASP's Authentication Cheat Sheet recommends reauthentication after risk events and before changing sensitive account information. Removing a login method belongs in that class of action, especially in fintech, where a stolen session should not be enough to reshape future access.
The word “independent” matters. Two aliases backed by the same external mailbox are two database rows but one practical recovery dependency. The policy evaluator should classify dependencies before it counts survivors. It should also reject a stale page version so two open tabs cannot each conclude that the other method will remain.
Fast is fine here.
On success, record an audit event with the actor, subject, opaque method ID, authentication context, request ID, and before/after recovery-state classification. Don't put credentials, provider tokens, full email addresses, or recovery codes in logs. The useful observability question is “which policy decision happened and why?”, not “what secret did the user present?”
When should fresh confirmation stage a login method removal?
Staged removal fits a fallback that looks recoverable but hasn't been proven recently enough for the account's risk policy. The first request creates a short-lived, single-purpose confirmation challenge; it does not remove the method. Completion repeats authorization against current state, consumes the challenge once, and commits the mutation. Cancellation and expiry leave the identity graph untouched.
This costs an extra interaction. The catch is that the extra step is wasted friction when a recent high-assurance authentication already proves what the policy needs. Use it for uncertainty you can resolve with fresh proof, not as ceremony on every row.
Instrument the stage boundary. Count challenges created, completed, expired, and superseded, then compare those rates by method type without attaching raw user identifiers to metric labels. A rising expiry rate can mean confusing copy, delayed delivery, or users abandoning a change they didn't intend. I'm not sure which one it is from a counter alone; sampled, privacy-reviewed traces or support categories are what resolve that ambiguity.
Pick staffed review when the last login path is at risk
When removal would leave no policy-eligible recovery path, the self-service action should stop before mutation. Offer enrollment of another verified path. If the user cannot complete that step because every credential is lost or account ownership is disputed, move the case into a staffed recovery process with its own evidence rules and audit trail.
Don't quietly turn support into an all-powerful bypass. The review outcome should be an explicit policy decision, and the reviewer should not be able to inspect authentication secrets. This option is slower and operationally expensive, but it is suitable for the narrow case where automated proof cannot establish continuity. It is not suitable for high-volume routine unlinking; use immediate or staged removal there.
For a full GDPR account-deletion request, stop treating the task as “remove the last method.” The workflow must prevent new login, revoke every active session, invalidate outstanding recovery challenges, and hand the account to the approved deletion process. That boundary avoids a dangerous half-state in which the profile looks deleted while a remembered session remains valid.
One switch. All sessions.
Implement one atomic removal and session-revocation flow
The implementation needs one authoritative decision point. In words, the diagram is: account page reads a versioned projection; command handler reauthenticates; policy engine evaluates the current recovery set; transaction mutates identities and sessions; audit sink receives the committed outcome; metrics aggregate decision codes. The projection makes the UI fast, but the transaction owns truth.
Here is a compact TypeScript model. The important bit is the mode split. Method removal preserves at least one recovery path. Account deletion revokes all sessions and removes all login methods as one terminal transition.
type LoginMethod = {
id: string;
verified: boolean;
enabled: boolean;
recoveryEligible: boolean;
dependency: string;
};
type AccountState = {
version: number;
methods: LoginMethod[];
sessionIds: string[];
};
type IdentityCommand =
| { mode: "remove-method"; methodId: string; expectedVersion: number }
| { mode: "delete-account"; expectedVersion: number };
type Decision =
| { kind: "allow-method-removal" }
| { kind: "allow-account-deletion" }
| { kind: "block"; code: "STALE_STATE" | "LAST_RECOVERY_PATH" };
function decide(state: AccountState, command: IdentityCommand): Decision {
if (state.version !== command.expectedVersion) {
return { kind: "block", code: "STALE_STATE" };
}
if (command.mode === "delete-account") {
return { kind: "allow-account-deletion" };
}
const survivingDependencies = new Set(
state.methods
.filter((method) =>
method.id !== command.methodId &&
method.verified &&
method.enabled &&
method.recoveryEligible
)
.map((method) => method.dependency)
);
return survivingDependencies.size > 0
? { kind: "allow-method-removal" }
: { kind: "block", code: "LAST_RECOVERY_PATH" };
}
The transaction must read the current account state again; evaluate the command; apply the identity and session writes; and append an audit record tied to the committed version. A uniqueness constraint on the request ID makes retries idempotent. For method removal, revoke sessions authenticated solely through the removed method when that relationship is available. For account deletion, revoke them all. Do not emit a success event before commit, because a dashboard that celebrates an uncommitted removal is worse than no dashboard at all.
Test the state transitions, not just the happy handler. Use a table of cases: one removable method with an independent survivor, two methods sharing one recovery dependency, an unverified survivor, a stale version, two concurrent removals, a retried request ID, and full account deletion with three active sessions. Assert the final methods, final sessions, decision code, and audit event together. A test that checks only the response can miss the exact partial state this design is meant to prevent.
Alerting should stay close to user impact. Watch the ratio of LAST_RECOVERY_PATH blocks to allowed removals, the age of staged confirmations, staffed-review backlog, and sessions remaining after a committed account deletion. Thresholds depend on normal traffic and risk appetite, so derive them from an observed baseline and review them with security, support, and privacy owners rather than copying a universal number.
Limits and decision rule
This field guide does not define how email, passkey, phone, or staffed recovery proves identity; each ceremony needs its own threat model. It also cannot settle legal retention policy. The engineering contract is narrower: expose no secret in the account page, make the post-change recovery state explicit, keep self-service removal atomic, and make full account deletion terminate every session.
Use immediate removal when verified independent recovery survives. Use staged removal when fresh proof can resolve uncertainty. Use staffed review when automation cannot establish ownership. If none of those paths can preserve or prove account control, don't mutate the identity graph.
Top comments (0)