For a marketplace, start with email verification as the least complex passwordless signup path, then require a stronger signal only when the expected abuse loss justifies the extra verification and recovery burden. The bill is not merely the price of sending a link or code: it is delivery + challenge attempts + manual review + account recovery + retained evidence, multiplied by both legitimate signups and automated retries. Instrument those terms before choosing a channel. The dominant term is the one with the largest measured contribution in your own ledger, not the line item that looks most expensive on a provider page.
The practical choice is an escalation policy, not one universal identity method. Use email for an ordinary buyer account, step up to phone when a transaction risk rule needs another possession signal, and offer OAuth when reduced form friction is worth accepting an external identity dependency. None of the three proves that a person is honest, unique, or entitled to transact.
That distinction matters. A verified destination proves control of a mailbox, phone number, or external account at a moment in time; marketplace authorization still has to decide who may list goods, withdraw funds, change payout details, or recover a locked account. Keep those decisions separate, or a low-risk signup shortcut quietly becomes the credential for a high-risk money movement.
What should marketplace passwordless onboarding choose: email verification, phone verification, or OAuth?
Choose by the action being unlocked and by the evidence you can safely retain. Email links usually create the smallest implementation and support surface for initial access. Phone codes add a second delivery ecosystem and can raise friction for users who cannot reliably receive messages. OAuth can reduce data entry, but availability, consent, account linking, and recovery now cross an organizational boundary.
The catch is that every option moves risk rather than deleting it. Email is not suitable as the only control for changing a payout destination after an account takeover signal. Phone should not be mandatory when international reach, accessibility, or number reassignment makes it a poor fit for the audience. OAuth should not be the sole path when the marketplace must remain accessible during an external identity dependency's unavailability. In those cases, keep email as the base path, apply transaction-specific step-up checks, and maintain a recovery process whose evidence is independent of the failed channel.
Use a decision table as a design review artifact, then replace qualitative labels with observed values from production telemetry:
| Concern | Email link | Phone code | OAuth |
|---|---|---|---|
| Initial user effort | Open a mailbox and follow a link | Receive and enter a code | Approve an external consent flow |
| Main dependency | Mail delivery and mailbox access | Messaging delivery and number access | External identity and redirect flow |
| Recovery question | What happens after mailbox loss? | What happens after number loss or reassignment? | What happens after external account loss? |
| Abuse control role | Reachability signal | Additional possession signal | External account signal |
| Best marketplace use | Ordinary account creation | Risk-triggered step-up | Optional low-friction entry |
I'm not sure which column will dominate your support cost without delivery, retry, abandonment, and recovery data segmented by country and account role. Nobody should be. A buyer browsing listings and a seller changing bank details do not deserve the same challenge policy merely because they share an accounts table.
Measure the bill as a verification ledger
Treat each authentication attempt as an auditable state transition. The ledger needs a stable operation identifier, the intended account or provisional subject, the channel, the policy decision, a coarse outcome, and timestamps. It should not become a warehouse of raw tokens, message bodies, phone numbers, or OAuth credentials. The more sensitive evidence you keep, the more expensive every access review, deletion request, incident investigation, and retention exception becomes.
A useful cost model is deliberately plain:
C_total = C_delivery + C_attempts + C_review + C_recovery + C_retention
Compute every term over the same cohort and time window. Then divide by completed, policy-eligible marketplace accounts rather than initiated challenges, because an inexpensive challenge that produces abandoned or unusable accounts is not inexpensive onboarding. Also break out retries: a bot that requests many challenges but never completes one belongs in the attempts term, not in a deceptively healthy conversion denominator.
Do this first.
The change that moves the dominant term should follow the measurement. If automated retries dominate, add rate limits and risk-based challenge issuance before replacing the delivery channel. If recovery dominates, redesign recovery evidence and support tooling. If retained event volume dominates, shorten the retention of low-value detail while preserving the minimum decision record required for audit and dispute handling. Retention is not a universal number; security, legal, privacy, and compliance owners must define it for the marketplace's jurisdictions and obligations.
An append-only event model makes retries explainable without pretending that message delivery is exactly once. The business operation is idempotent; transports may repeat.
package signup
import (
"context"
"errors"
"time"
)
type StartRequest struct {
OperationID string
SubjectHash string
Channel string
}
type Decision struct {
OperationID string
Outcome string
OccurredAt time.Time
}
type Ledger interface {
Find(ctx context.Context, operationID string) (Decision, bool, error)
Append(ctx context.Context, decision Decision) error
}
type Challenger interface {
Issue(ctx context.Context, subjectHash, channel string) error
}
func Start(ctx context.Context, req StartRequest, ledger Ledger, challenger Challenger, now time.Time) (Decision, error) {
if req.OperationID == "" || req.SubjectHash == "" {
return Decision{}, errors.New("missing operation identity")
}
if prior, found, err := ledger.Find(ctx, req.OperationID); err != nil {
return Decision{}, err
} else if found {
return prior, nil
}
if err := challenger.Issue(ctx, req.SubjectHash, req.Channel); err != nil {
return Decision{}, err
}
decision := Decision{
OperationID: req.OperationID,
Outcome: "challenge_issued",
OccurredAt: now.UTC(),
}
if err := ledger.Append(ctx, decision); err != nil {
return Decision{}, err
}
return decision, nil
}
In a production design, challenge issuance and ledger persistence need a durable coordination mechanism so a process interruption cannot make the record disagree with the delivery request. The interface above exposes the invariant but does not prescribe a database transaction, outbox, or queue; that choice depends on the storage and delivery boundaries already operated by the team. Exactly-once delivery is the wrong promise. Exactly-once business effect, enforced by an idempotency key and a unique ledger constraint, is the useful target.
Separate abuse resistance from account proof
A bot-resistant signup flow starts before any email, phone, or OAuth call. Rate-limit by several privacy-conscious signals, apply velocity rules to both challenge creation and completion, expire challenges, make them single use, and return responses that do not reveal whether an account exists. OWASP's Authentication Cheat Sheet specifically treats generic authentication responses, automated-attack controls, logging, monitoring, and reauthentication as parts of the authentication system rather than optional polish.
Keep the public response stable while recording a more precise internal reason. For example, the client can receive an accepted response whether a safe challenge was issued or the request was suppressed, while the audit stream records issued, rate_limited, or duplicate_operation. Avoid stuffing raw identifiers into that stream. Use a keyed, rotating pseudonymous value when correlation is necessary, restrict access to the key material, and document which investigations become impossible after rotation.
Short-lived verification artifacts should be random, scoped to one purpose, bound to the intended flow, and invalidated after successful use. OAuth requires additional protocol-specific validation; it is not interchangeable with accepting an email field returned by any external endpoint. Authorization remains local. A successful external sign-in can establish a session, but seller activation, listing privileges, payouts, and recovery still need marketplace policy decisions with their own audit records.
This is where apparently harmless account linking becomes dangerous. Two sign-in methods that present the same email text are not automatically the same principal. Link methods only through a deliberate authenticated ceremony, record who initiated it and which policy approved it, and notify the existing recovery channel without including secrets. Otherwise an identity normalization rule can turn a second signup path into an account takeover path.
No magic here.
Test policy transitions, not just happy-path delivery
The test suite should model a state machine: provisional, challenge_issued, verified, active, step_up_required, and recovery_pending are examples of states whose legal transitions must be explicit. A repeated completion request must produce one account activation event. An expired challenge must not be revived by a retry. A newer challenge should define what happens to older artifacts. Concurrent requests using the same operation identifier must converge on the same recorded decision.
Deployment deserves the same discipline. Introduce a new verification policy in observe-only mode, log the decision it would have made, and compare completion, suppression, review, and recovery outcomes by cohort before enforcement. Do not log secrets to make comparison easier. Dashboards should distinguish provider delivery acceptance from user verification completion, because those events answer different operational questions; alerts should focus on deviations in challenge issuance, completion, suppression, and recovery rather than a single aggregate success rate.
Error handling must preserve ambiguity at the public boundary and specificity inside the controlled audit boundary. Retry only operations known to be idempotent, cap attempts, and attach the original operation identifier. Support tools need a timeline that explains policy decisions without displaying authentication artifacts. This costs engineering time, but it is cheaper than making operators infer account history from scattered application logs while a withdrawal is waiting.
Before launch, run abuse tests for enumeration, replay, challenge flooding, concurrent completion, account linking, recovery-channel replacement, and privilege changes after session establishment. Also test accessibility and international delivery assumptions with the actual audience. Your mileage may vary by geography and account mix, which is precisely why the rollout needs cohorts rather than one global switch.
Keep less, and accept the investigative limit
After aggregate cost and policy tuning stabilize, delete expired challenge values, delivery payloads, and unnecessary raw destination data on the approved schedule. Retain the smaller decision trail: operation identity, pseudonymous subject, policy version, coarse outcome, event time, and the actor or service responsible for privileged changes. Protect that trail against unauthorized alteration and audit access to it.
There is a real trade-off. Aggressive minimization can prevent an investigator from reconstructing the exact content or destination of an old message; longer retention can increase privacy, compliance, and breach impact. The correct boundary comes from documented threat models, dispute windows, and applicable obligations, not an authentication tutorial. Record the deletion policy itself as a versioned decision so an auditor can explain why evidence exists for one period and not another.
The final architecture is therefore intentionally uneven: inexpensive, low-friction entry for ordinary marketplace access; stronger verification for risky actions; an idempotent decision ledger across both; and recovery that does not collapse when the original channel disappears. Email, phone, and OAuth are inputs to that policy. They are not the policy.
References
- OWASP Authentication Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
Top comments (0)