You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Passport.js Local Strategy实现手机号+邮箱登录的疑问

实现Passport.js Local Strategy支持邮箱/手机号登录的解决方案

首先直接回答你的问题:不需要添加第三个字段,但需要调整Mongoose模型的配置和Passport Strategy的验证逻辑。passport-local-mongoose默认只认一个usernameField,但我们可以自定义验证逻辑,判断用户输入的是邮箱还是手机号,再去数据库匹配用户。

步骤1:调整Mongoose模型

你的现有模型已经包含email和phoneNumber字段,但需要优化字段约束,同时保留passport-local-mongoose的密码处理能力:

import mongoose from 'mongoose';
import passportLocalMongoose from 'passport-local-mongoose';

const Schema = mongoose.Schema;

const AccountSchema = new Schema({
  phoneNumber: { 
    type: String, 
    unique: true, 
    sparse: true // 允许字段为空,但非空时必须唯一
  },
  email: { 
    type: String, 
    unique: true, 
    sparse: true 
  },
  myId: String,
  termsandcondition: Boolean,
  verifiedEmailCode: String,
  verifiedEmail: { type: Boolean, default: false },
  verifiedPhone: { type: Boolean, default: false }
});

// 配置passport-local-mongoose,保留默认的usernameField但后续会自定义验证逻辑
AccountSchema.plugin(passportLocalMongoose, { usernameField: 'email' });

export default mongoose.model('Account', AccountSchema);

步骤2:自定义Passport Local Strategy验证逻辑

原来的Account.authenticate()是默认逻辑,只能匹配单一字段。我们需要重写验证函数,判断用户输入的是邮箱还是手机号:

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const Account = require('./model/account');

passport.use(new LocalStrategy({
  usernameField: 'loginIdentifier', // 前端统一传这个字段,值可以是邮箱或手机号
  passwordField: 'password'
}, async (loginIdentifier, password, done) => {
  try {
    // 判断输入类型:包含@则为邮箱,否则为手机号
    const user = loginIdentifier.includes('@')
      ? await Account.findOne({ email: loginIdentifier })
      : await Account.findOne({ phoneNumber: loginIdentifier });

    if (!user) {
      return done(null, false, { message: '用户不存在' });
    }

    // 用passport-local-mongoose内置的密码验证方法
    const isPasswordValid = await user.authenticate(password);
    if (!isPasswordValid) {
      return done(null, false, { message: '密码错误' });
    }

    return done(null, user);
  } catch (err) {
    return done(err);
  }
}));

passport.serializeUser(Account.serializeUser());
passport.deserializeUser(Account.deserializeUser());

步骤3:调整登录路由适配新字段

前端登录时需要将原来的email参数改为loginIdentifier(或者你也可以让前端同时传email/phoneNumber,后端优先判断,但统一字段更清晰):

api.post('/login', (req, res, next) => passport.authenticate(
  'local',
  { session: false, scope: [] },
  (err, user, info) => {
    if (err) {
      return next(err);
    }
    if (!user) {
      return res.json({
        "status": false,
        "code": 401,
        "message": info.message || '登录失败'
      });
    }
    if (!user.verifiedEmail && !user.verifiedPhone) {
      return res.json({
        "status": false,
        "code": 403,
        "message": "You need to verify your email or Phone number"
      });
    }
    req.logIn(user, next);
  }
)(req, res, next), generateAccessToken, respond);

额外说明

  • 注册逻辑可以同步优化:允许用户选择用邮箱或手机号注册,注册时对应填充email或phoneNumber字段,username可以复用其中一个(或者留空,passport-local-mongoose会自动处理,但建议保持一致性)。
  • 字段唯一性约束用sparse: true是为了允许用户只填邮箱或手机号中的一个,避免强制要求两个字段都存在。

内容的提问来源于stack exchange,提问作者elizabeth

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:46:49