【问题标题】:How to confirm email address using express/node?如何使用 express/node 确认电子邮件地址?
【发布时间】:2016-12-29 18:56:40
【问题描述】:

我正在尝试为用户构建电子邮件地址验证,以验证他们的电子邮件是真实的。我应该使用什么包来确认用户的电子邮件地址?到目前为止,我使用猫鼬和快递

代码示例

var UserSchema = new mongoose.Schema({
    email: { type: String, unique: true, lowercase: true }
    password: String
});

var User = mongoose.model('User', UserSchema);

app.post('/signup', function(req, res, next) {
   // Create a new User
   var user = new User();
   user.email = req.body.email;
   user.password = req.body.password;
   user.save();
});

在 app.post 代码中,我如何确认用户的电子邮件地址?

【问题讨论】:

  • 首先,验证输入的电子邮件是一个有效的电子邮件字符串。
  • 然后,在 User 模型中有一个名为“emailValidated”的属性。向提交的电子邮件发送一封电子邮件,并附上一个独特的链接。如果访问该链接,请将“emailValidated”设置为 true。只有当用户可以访问提供的电子邮件时才能访问该链接,从而确认用户的电子邮件。

标签: node.js express


【解决方案1】:

您要查找的内容称为“帐户验证”或“电子邮件验证”。有很多 Node 模块可以执行此操作,但原理如下:

  • 您的 User 模型应该有一个 active 属性,默认为 false
  • 当用户提交有效的注册表单时,创建一个新用户(active 最初将是false
  • 使用加密库创建一个长的随机字符串(通常最好 128 个字符)并将其存储在您的数据库中并参考用户 ID
  • 向提供的电子邮件地址发送一封电子邮件,并将哈希作为指向服务器上路由的链接的一部分
  • 当用户单击链接并点击您的路线时,检查 URL 中传递的哈希
  • 如果数据库中存在哈希,则获取相关用户并将其active属性设置为true
  • 从数据库中删除哈希,不再需要它

您的用户现已通过验证。

【讨论】:

  • 感谢您。但是有一个问题,我们如何确保用于确认电子邮件的公共 url 端点不会被发送垃圾邮件? (因此会导致数据库性能问题)。我想我们应该实施身份验证?这将如何运作?
  • 感谢@Soviut 的清晰解释...您认为社交注册需要电子邮件验证,还是没有必要? (很抱歉这个很晚的评论...... :-)
  • 你错过了一个步骤,除了 OP 不关心令牌过期,但如果安全性很重要(应该如此),那么令牌应该设置为在给定时间段(可能 6 小时)后过期
  • @Fortune 是的。您将存储创建的日期,然后在检查令牌时进行比较。如果超过了创建日期+有效期,请删除它。
  • @DashiellRoseBark-Huss 这将允许不良行为者通过使用受害者的电子邮件地址注册然后进行验证来冒充某人,而无需访问受害者的电子邮件帐户,从而使整个电子邮件验证过程无效。
【解决方案2】:
var express=require('express');
var nodemailer = require("nodemailer");
var app=express();
/*
    Here we are configuring our SMTP Server details.
    STMP is mail server which is responsible for sending and recieving email.
*/
var smtpTransport = nodemailer.createTransport("SMTP",{
    service: "Gmail",
    auth: {
        user: "Your Gmail ID",
        pass: "Gmail Password"
    }
});
var rand,mailOptions,host,link;
/*------------------SMTP Over-----------------------------*/

/*------------------Routing Started ------------------------*/

app.get('/',function(req,res){
    res.sendfile('index.html');
});
app.get('/send',function(req,res){
        rand=Math.floor((Math.random() * 100) + 54);
    host=req.get('host');
    link="http://"+req.get('host')+"/verify?id="+rand;
    mailOptions={
        to : req.query.to,
        subject : "Please confirm your Email account",
        html : "Hello,<br> Please Click on the link to verify your email.<br><a href="+link+">Click here to verify</a>" 
    }
    console.log(mailOptions);
    smtpTransport.sendMail(mailOptions, function(error, response){
     if(error){
            console.log(error);
        res.end("error");
     }else{
            console.log("Message sent: " + response.message);
        res.end("sent");
         }
});
});

app.get('/verify',function(req,res){
console.log(req.protocol+":/"+req.get('host'));
if((req.protocol+"://"+req.get('host'))==("http://"+host))
{
    console.log("Domain is matched. Information is from Authentic email");
    if(req.query.id==rand)
    {
        console.log("email is verified");
        res.end("<h1>Email "+mailOptions.to+" is been Successfully verified");
    }
    else
    {
        console.log("email is not verified");
        res.end("<h1>Bad Request</h1>");
    }
}
else
{
    res.end("<h1>Request is from unknown source");
}
});

/*--------------------Routing Over----------------------------*/

app.listen(3000,function(){
    console.log("Express Started on Port 3000");
});

按照代码示例,可以使用nodemailer发送链接,然后验证。 这是一个链接:https://codeforgeek.com/2014/07/node-email-verification-script/

【讨论】:

  • 我不明白你为什么要检查主机和协议是否相同。如果它们不一样,请求甚至不会到达我们的服务器吗?
  • 此邮箱验证码必须在主app.jsindex.router.js?
  • 我想只要 /send 和 /verify 路由到达同一个服务器程序,这将起作用。我的意思是,如果 /send 请求被发送到服务器程序并且由于某种原因服务器崩溃并重新启动,则变量 rand 将不再可用于使用 /verify 路由验证用户。变量 rand 应该存储在数据库中。
  • rand 应该使用加密库创建:(
  • @Souban 是真的,您需要将随机文件保存在数据库中。
【解决方案3】:

第 1 步:

用户模型

var userSchema = new mongoose.Schema({
    email: { type: String, unique: true },
    isVerified: { type: Boolean, default: false },
    password: String,
  });

代币模型

const tokenSchema = new mongoose.Schema({
    _userId: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'User' },
    token: { type: String, required: true },
    expireAt: { type: Date, default: Date.now, index: { expires: 86400000 } }
});

第 2 步:登录

exports.login = function(req, res, next) {
    User.findOne({ email: req.body.email }, function(err, user) {
        // error occur
        if(err){
            return res.status(500).send({msg: err.message});
        }
        // user is not found in database i.e. user is not registered yet.
        else if (!user){
            return res.status(401).send({ msg:'The email address ' + req.body.email + ' is not associated with any account. please check and try again!'});
        }
        // comapre user's password if user is find in above step
        else if(!Bcrypt.compareSync(req.body.password, user.password)){
            return res.status(401).send({msg:'Wrong Password!'});
        }
        // check user is verified or not
        else if (!user.isVerified){
            return res.status(401).send({msg:'Your Email has not been verified. Please click on resend'});
        } 
        // user successfully logged in
        else{
            return res.status(200).send('User successfully logged in.');
        }
    });

});

第 3 步:注册

exports.signup = function(req, res, next) {
  User.findOne({ email: req.body.email }, function (err, user) {
    // error occur
    if(err){
        return res.status(500).send({msg: err.message});
    }
    // if email is exist into database i.e. email is associated with another user.
    else if (user) {
        return res.status(400).send({msg:'This email address is already associated with another account.'});
    }
    // if user is not exist into database then save the user into database for register account
    else{
        // password hashing for save into databse
        req.body.password = Bcrypt.hashSync(req.body.password, 10);
        // create and save user
        user = new User({ name: req.body.name, email: req.body.email, password: req.body.password });
        user.save(function (err) {
            if (err) { 
              return res.status(500).send({msg:err.message});
            }
            
            // generate token and save
            var token = new Token({ _userId: user._id, token: crypto.randomBytes(16).toString('hex') });
            token.save(function (err) {
              if(err){
                return res.status(500).send({msg:err.message});
              }

                // Send email (use credintials of SendGrid)
                var transporter = nodemailer.createTransport({ service: 'Sendgrid', auth: { user: process.env.SENDGRID_USERNAME, pass: process.env.SENDGRID_PASSWORD } });
                var mailOptions = { from: 'no-reply@example.com', to: user.email, subject: 'Account Verification Link', text: 'Hello '+ req.body.name +',\n\n' + 'Please verify your account by clicking the link: \nhttp:\/\/' + req.headers.host + '\/confirmation\/' + user.email + '\/' + token.token + '\n\nThank You!\n' };
                transporter.sendMail(mailOptions, function (err) {
                    if (err) { 
                        return res.status(500).send({msg:'Technical Issue!, Please click on resend for verify your Email.'});
                     }
                    return res.status(200).send('A verification email has been sent to ' + user.email + '. It will be expire after one day. If you not get verification Email click on resend token.');
                });
            });
        });
    }
    
  });

});

第 4 步:验证帐户

// It is GET method, you have to write like that
//    app.get('/confirmation/:email/:token',confirmEmail)

exports.confirmEmail = function (req, res, next) {
    Token.findOne({ token: req.params.token }, function (err, token) {
        // token is not found into database i.e. token may have expired 
        if (!token){
            return res.status(400).send({msg:'Your verification link may have expired. Please click on resend for verify your Email.'});
        }
        // if token is found then check valid user 
        else{
            User.findOne({ _id: token._userId, email: req.params.email }, function (err, user) {
                // not valid user
                if (!user){
                    return res.status(401).send({msg:'We were unable to find a user for this verification. Please SignUp!'});
                } 
                // user is already verified
                else if (user.isVerified){
                    return res.status(200).send('User has been already verified. Please Login');
                }
                // verify user
                else{
                    // change isVerified to true
                    user.isVerified = true;
                    user.save(function (err) {
                        // error occur
                        if(err){
                            return res.status(500).send({msg: err.message});
                        }
                        // account successfully verified
                        else{
                          return res.status(200).send('Your account has been successfully verified');
                        }
                    });
                }
            });
        }
        
    });
});

第 5 步:重新发送链接

exports.resendLink = function (req, res, next) {

    User.findOne({ email: req.body.email }, function (err, user) {
        // user is not found into database
        if (!user){
            return res.status(400).send({msg:'We were unable to find a user with that email. Make sure your Email is correct!'});
        }
        // user has been already verified
        else if (user.isVerified){
            return res.status(200).send('This account has been already verified. Please log in.');
    
        } 
        // send verification link
        else{
            // generate token and save
            var token = new Token({ _userId: user._id, token: crypto.randomBytes(16).toString('hex') });
            token.save(function (err) {
                if (err) {
                  return res.status(500).send({msg:err.message});
                }
    
                // Send email (use credintials of SendGrid)
                    var transporter = nodemailer.createTransport({ service: 'Sendgrid', auth: { user: process.env.SENDGRID_USERNAME, pass: process.env.SENDGRID_PASSWORD } });
                    var mailOptions = { from: 'no-reply@example.com', to: user.email, subject: 'Account Verification Link', text: 'Hello '+ user.name +',\n\n' + 'Please verify your account by clicking the link: \nhttp:\/\/' + req.headers.host + '\/confirmation\/' + user.email + '\/' + token.token + '\n\nThank You!\n' };
                    transporter.sendMail(mailOptions, function (err) {
                       if (err) { 
                        return res.status(500).send({msg:'Technical Issue!, Please click on resend for verify your Email.'});
                     }
                    return res.status(200).send('A verification email has been sent to ' + user.email + '. It will be expire after one day. If you not get verification Email click on resend token.');
                });
            });
        }
    });
});

您可以通过此链接获得帮助:https://medium.com/@slgupta022/email-verification-using-sendgrid-in-node-js-express-js-mongodb-c5803f643e09

【讨论】:

  • 虽然这可能会回答问题,但如果您在答案中链接到自己的网站或服务,you must disclose your affiliation in the answer yourself。如果没有此披露,这可能会被视为垃圾邮件,并可能导致链接被删除或您的帖子被标记为此类。
  • npm install bcryptjs --save
  • npm install crypto --save
  • npm install nodemailer --save
  • 重新发送链接时,不应该检查一个令牌是否仍然存在并在生成新令牌之前先删除该令牌吗?
【解决方案4】:

我想提出一种与提议的方法略有不同的方法。

此方法不会将哈希放入数据库(因此与它的交互较少)

您不需要在数据库中注册哈希。以下是收到注册请求后的概览:

  1. 您对用户 ID + 注册时间进行编码
  2. 您将令牌发送给用户
  3. 当用户触发他的注册请求时,您解码令牌。
  4. 因为解码后的token包含用户id+时间,所以可以 通过增加角色将用户标记为已注册 (注册、订阅者、管理员等)例如

翻译成代码,你会得到这样的:

1- 对令牌进行编码

function encodeRegistrationToken()
{
    // jsonweb automatically adds a key that determines the time, but you can use any module
    const jwt = require('jsonwebtoken');

    // The information we need to find our user in the database (not sensible info)
    let info = {id: yourUserId};

    // The hash we will be sending to the user
    const token = jwt.sign(info, "yoursecretkey");

    return token;
}

// ... 
let token = encodeRegistrationToken();

2- 通过任何适当的方式向用户发送令牌

// Your implementation of sending the token
sendTokenToUser(token);

3- 解码令牌

function decodeRegistrationToken(token)
{   
    const jwt = require('jsonwebtoken');
    let decoded = jwt.verify(token, "yoursecretkey");

    let userId = decoded.id;

    // Check that the user didn't take too long
    let dateNow = new Date();
    let tokenTime = decoded.iat * 1000;

    // Two hours
    let hours = 2;
    let tokenLife = hours * 60 * 1000;

    // User took too long to enter the code
    if (tokenTime + tokenLife < dateNow.getTime())
    {
        return {            
            expired: true
        };
    }

    // User registered in time
    return {
        userID
    };

}

4 - 更新您的数据库

  • 将用户角色升级为订阅者

  • 将他们的“注册”键设置为 true

快速说明:如果需要,您可以在编码令牌时进一步编码用户 ID(它很容易访问)。

【讨论】:

    【解决方案5】:

    我花了很多时间找出发送确认邮件的完美方式。这是我使用的方法。

    const jwt = require('jsonwebtoken');
    const nodemailer = require("nodemailer");
    

    第 1 步 使用过期日期在 jwt 令牌中编码用户 ID

    var date = new Date();
    var mail = {
                "id": user.id,
                "created": date.toString()
                }
    
    const token_mail_verification = jwt.sign(mail, config.jwt_secret_mail, { expiresIn: '1d' });
    
    var url = config.baseUrl + "verify?id=" + token_mail_verification;
    

    第 2 步 使用 nodemailer 库将令牌发送到用户电子邮件地址

     let transporter = nodemailer.createTransport({
            name: "www.domain.com",
            host: "smtp.domain.com",
            port: 323,
            secure: false, // use SSL
            auth: {
                user: "user@domain.com", // username for your mail server
                pass: "Password", // password
            },
    
        });
    
        // send mail with defined transport object
        let info = await transporter.sendMail({
            from: '"NAME" <user@domain.com>', // sender address
            to: user.email, // list of receivers seperated by comma
            subject: "Account Verification", // Subject line
            text: "Click on the link below to veriy your account " + url, // plain text body
        }, (error, info) => {
    
            if (error) {
                console.log(error)
                return;
            }
            console.log('Message sent successfully!');
            console.log(info);
            transporter.close();
        });
    

    第 3 步 接受验证链接

    app.get('/verify', function(req, res) {
        token = req.query.id;
        if (token) {
            try {
                jwt.verify(token, config.jwt_secret_mail, (e, decoded) => {
                    if (e) {
                        console.log(e)
                        return res.sendStatus(403)
                    } else {
                        id = decoded.id;
    
                    
    //Update your database here with whatever the verification flag you are using 
    
    
    
                    }
    
                });
            } catch (err) {
    
                console.log(err)
                return res.sendStatus(403)
            }
        } else {
            return res.sendStatus(403)
    
        }
    
    })
    

    第 4 步 喝杯咖啡,感谢我为您节省了这么多时间

    PS:这个 nodemailer SMTP 方法甚至可以与您的主机一起使用。所以没必要找第三方。您还可以找到将 gmail 与 nodemailer 一起使用的方法。

    【讨论】:

    • 请注意安全漏洞!在第一次验证后没有使 JWT 令牌失效的情况下,在剩下的 1 天内,该端点仍然打开以直接编辑用户存储的信息。为避免这种情况,将 jwt 存储在数据库中,然后在第一次验证时检查是否匹配,然后将其从数据库中销毁,以避免将来多次验证请求。
    • @Nicolò 为什么多个验证请求会缺乏安全性?
    • 我不希望在不再需要私人信息时立即将它们保存在 jwt 中。不是很大的危险,但要牢记,因为“/verify”请求中的服务器必须只信任它,没有会话,没有身份验证,没有层。
    【解决方案6】:

    如果您只是在本地机器上进行测试,了解如何做的一种简单方法是:

    假设您已经知道通过 nodemailer 发送邮件..

    一旦用户注册,在您的数据库中存储注册数据后,在您的服务器端从收到的注册数据和随机生成的数字中获取用户电子邮件,并使用用户所在页面的地址构建一个自定义 URL在他/她点击邮件中给出的链接后定向。

    var customUrl = "http://"+ your host + "/" + your verification web-page + "?email=" + userEmail + "&id=" + randomNumber;
    

    一个例子可以是:

    var userEmail = someone@example.com
    var host = localhost:8080
    var directWebPage = verifyUserEmail.html
    var randomNumber = // generate with math.random() // lets say 111
    

    将上面的customUrl格式放入它看起来像这样

    customUrl:http://localhost:8080/verifyUserEmail.htmlemail=someone@example.com&id=111
    

    将此 customUrl 保存在某处(可能在您的数据库中) 现在,向用户发送一封电子邮件,其中包含此 cutomUrl 链接的电子邮件正文。

    <a href="customUrl">Click to verify your email</a>
    

    当用户点击链接时,他/她将被引导到 verifyUserEmail.html 页面,当这种情况发生时,您可以提取页面 url 包含 电子邮件id 信息

    例如,在 Angular 中我会这样-

    var urlVerifyData = $location.url(); or $location.absUrl();
    

    现在使用 javascript 字符串方法从 urlVerifyData 字符串中提取 email

    使用此 电子邮件urlVerifyData

    请求您的服务器

    现在在您的数据库中查询此电子邮件,并使用用户的 urlVerifyData

    验证之前存储的 customUrl

    如果他们匹配,你好!你让自己成为了真正的用户!!!

    【讨论】:

      【解决方案7】:
          function generateLink() {
              var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
              var token = '';
              for (var i = 16; i > 0; --i) {
                  var rand = Math.round(Math.random() * (chars.length - 1))
                  token += chars[rand];
              }
              var link = "http://localhost" + "/verify?id=" + token;
              return link;
          }
      
          // npm install @sendGrid/mail --save
      
          //library for generating link using SendGrid
          const sgMail = require('@sendgrid/mail');
          sgMail.setApiKey("SENDGRID_API_KEY"); //create an account on sendgrid and get an API key
      
          // generated link is send to the user's email for email verification
          let sendVerifyEmailLink = (req, res) => {
              var link = generateLink();
              const msg = {
                  to: 'test@gmail.com',
                  from: 'test@gmail.com',
                  subject: 'Account Verifictaion',
                  text: 'Hello,\n\n' + 'Please verify your account by clicking the link:\/\/\n',
                  html: 'Hello,\n\n <br> Please verify your account by clicking the link: \n <br> <strong><a href = ' + link + '>http:\/\/ Click here to verify the given Link </a></strong>.\n .<br>Thanks<br>',
              };
              sgMail.send(msg).then(() => { }, error => {
                  console.error(error);
      
                  if (error.response) {
                      console.error(error.response.body)
                  }
              });
              console.log(msg)
          }
      

      【讨论】:

      • 此代码生成(不安全的)随机字符串链接并通过电子邮件发送。 OP询问的验证码/逻辑在哪里?
      猜你喜欢
      • 2023-03-14
      • 1970-01-01
      • 2021-02-26
      • 1970-01-01
      • 1970-01-01
      • 2017-07-23
      • 2023-03-07
      • 2011-11-07
      • 1970-01-01
      相关资源
      最近更新 更多