Dropping Email Required: Migrating to Phone-or-Email Authentication
Our registration form said "just enter your phone number," but the backend still rejected requests without an email address. A minor bug, but real users were stuck. Here's how we fixed it.
Background
Our LINE-integrated e-commerce platform targets users who often lack email addresses or prefer not to provide them. Despite our registration form showing "phone number only," submissions consistently failed with 400 Bad Request errors.
The root cause was simple: our frontend sent only phone numbers, but backend validation still enforced email: required.
The Solution
Frontend Changes (Vue + Vite)
Made the email field optional and updated the label accordingly:
<input
v-model="form.email"
type="email"
placeholder="Email address (optional)"
/>
Removed email validation from the client-side submission logic.
Backend Changes (Node.js / ts-node)
Updated the /api/auth/register endpoint validation:
// Before
const schema = z.object({
phone: z.string(),
email: z.string().email(), // ← This was the problem
password: z.string().min(6),
});
// After
const schema = z.object({
phone: z.string().optional(),
email: z.string().email().optional(),
password: z.string().min(6),
}).refine(data => data.phone || data.email, {
message: 'Either phone or email is required',
});
Login was similarly updated to search by either phone or email:
const user = await User.findOne({
where: phone ? { phone } : { email },
});
Pitfalls
Existing User Conflicts
During testing, we encountered "phone number already registered" errors. Investigation revealed that test admin accounts had been assigned real user phone numbers. This highlighted the risks of developing against production data.
Our solution: when users hit this error, we now guide them to the login flow with "Already registered? Please log in instead" messaging.
Key Takeaways
"Required" fields depend entirely on user context.
While email addresses feel natural to developers, phone numbers are far more familiar to end users—especially older users or those with lower tech literacy. For B2C services targeting these demographics, authentication UX directly impacts conversion rates.
Zod's .refine() method is powerful.
"Either A or B is required" logic can't be expressed through individual field validation. The .refine() method enables clean object-level validation rules.
Top comments (0)