Node.js 기반 홈페이지 만들기 (3) - 회원가입 이메일 인증

 

시리즈

기능 동작이 목표기 때문에 자세한 설명은 하지 않습니다.

  • IDE : WebStorm
  • DB : MongoDB
  • OS : MAC

시작하기 전

전의 실습용으로 쓰던 페이지의 디자인이 너무 투박해서 https://codepen.io/veronicadev/pen/YYvjzO를 사용해 전체적으로 디자인을 변경하였습니다.

14

그리고, Tesseract로 페이지의 주제를 잡아주었습니다.

13

이메일 인증

요즘은 회원가입 시 EMAIL인증을 필수로 해야합니다. 이메일로 인증코드를 전송하는 방법과 url을 클릭하게 하는 방법이 대표적입니다. 저는 URL을 클릭해서 인증하는 코드를 구현할 예정입니다.

Node.js에는 이메일 전송을 도와주는 NODEMAILER라는 유명한 모듈이 있습니다. 자세한 내용은 nodemailer.com에서 확인바랍니다.

먼저, NODEMAILER를 설치합니다. 추가로 smtp 서버를 사용하기 위한 모듈인 nodemailer-smtp-transport도 설치합니다.

npm install nodemailer
npm install nodemailer-smtp-transport

NODEMAILER의 기본적인 사용법이며, 공식 DOCS에서 발췌하였습니다.

"use strict";
const nodemailer = require("nodemailer");

// async..await is not allowed in global scope, must use a wrapper
async function main(){

  // Generate test SMTP service account from ethereal.email
  // Only needed if you don't have a real mail account for testing
  let testAccount = await nodemailer.createTestAccount();

  // create reusable transporter object using the default SMTP transport
  let transporter = nodemailer.createTransport({
    host: "smtp.ethereal.email",
    port: 587,
    secure: false, // true for 465, false for other ports
    auth: {
      user: testAccount.user, // generated ethereal user
      pass: testAccount.pass // generated ethereal password
    }
  });

  // send mail with defined transport object
  let info = await transporter.sendMail({
    from: '"Fred Foo 👻" <[email protected]>', // sender address
    to: "[email protected], [email protected]", // list of receivers
    subject: "Hello ✔", // Subject line
    text: "Hello world?", // plain text body
    html: "<b>Hello world?</b>" // html body
  });

  console.log("Message sent: %s", info.messageId);
  // Message sent: <[email protected]>

  // Preview only available when sending through an Ethereal account
  console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info));
  // Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
}

main().catch(console.error);

이메일인증을 위해 발급할 인증코드인증여부를 저장할 스키마를 추가합니다.

인증 여부의 default는 false로, 인증이 되면 true로 변경하도록 할 예정입니다.

models/user.js

    //인증여부
    email_verified :{ type: Boolean, required:true, default: false },
    //인증코드
    key_for_verify :{ type: String, required:true },

위에서 설치한 메일모듈을 사용하기 위해 require합니다.

routes/index.js

var nodemailer = require('nodemailer');
var smtpTransporter=require('nodemailer-smtp-transport');

인증을 위해 사용할 코드는 비밀번호를 해시처리할 때 사용한 Crypto모듈을 사용하여 생성합니다.

randomBytes를 사용하여 256자의 랜덤값을 만든 후, hex로 인코딩하여 100번째부터 5자 그리고 base64로 인코딩하여 50번째부터 5자만 잘라낸 후 합칩니다.

  • 인증 코드를 생성하는 여러가지 방법이 있지만 저는 이 방법을 선호합니다.
var key_one=crypto.randomBytes(256).toString('hex').substr(100, 5);
var key_two=crypto.randomBytes(256).toString('base64').substr(50, 5);
var key_for_verify=key_one+key_two;

회원가입 시 모델을 생성하는 부분에 코드를 추가합니다.

routes/index.js

var key_one=crypto.randomBytes(256).toString('hex').substr(100, 5);
var key_two=crypto.randomBytes(256).toString('base64').substr(50, 5);
var key_for_verify=key_one+key_two;

const user = new User({
                    _id: new mongoose.Types.ObjectId(),
                    ...,
                    key_for_verify: key_for_verify
                });

메일 전송을 위해 오브젝트를 생성합니다.

저는 Gmail을 사용했으며, myaccount.google.com/lesssecureapps에서 보안설정을 해주어야합니다.

routes/index.js

var smtpTransport = nodemailer.createTransport(smtpTransporter({
    service: 'Gmail',
    host:'smtp.gmail.com',
    auth: {
        user: '[email protected]',
        pass: 'password'
    }
}));

회원가입과 동시에 메일이 전송되도록 코드를 추가합니다.

routes/index.js

  .then(result => {
                    console.log(result);
                    //url
                    var url = 'https://' + req.get('host')+'/confirmEmail'+'?key='+key_for_verify;
                    //옵션
                    var mailOpt = {
                        from: '[email protected]',
                        to: user.email,
                        subject: '이메일 인증을 진행해주세요.',
                        html : '<h1>이메일 인증을 위해 URL을 클릭해주세요.</h1><br>'+url
                    };
                    //전송
                    smtpTransport.sendMail(mailOpt, function(err, res) {
                        if (err) {
                            console.log(err);
                        } else {
                             console.log('email has been sent.');
                        }
                        smtpTransport.close();
                    });
                    res.send('<script type="text/javascript">alert("이메일을 확인하세요."); window.location="/"; </script>');

회원가입을 해보면 이메일이 전송되는 것을 확인할 수 있습니다.

15

메일에서 url을 클릭하면, 인증처리를 해야합니다.

  • /confirmEmail로 접속하면,
  • url의 key(req.query.key)와 같은 코드(key_for_verify)가 DB에 있는 지 찾습니다.
  • 같은 코드가 있다면, 그 유저의 email_verified를 true로 변경합니다.

routes/index.js

router.get('/confirmEmail',function (req,res) {

    User.updateOne({key_for_verify:req.query.key},{$set:{email_verified:true}}, function(err,user){
        //에러처리
        if (err) {
            console.log(err);
        }
        //일치하는 key가 없으면
        else if(user.n==0){
            res.send('<script type="text/javascript">alert("Not verified"); window.location="/"; </script>');
        }
        //인증 성공
        else {
            res.send('<script type="text/javascript">alert("Successfully verified"); window.location="/"; </script>');
        }
    });
});

마지막으로, email_verifiedtrue인 유저만 로그인이 되도록 로그인 처리 부분에 조건을 추가합니다.

routes/index.js

 User.findOne({..., email_verified:true}

마치며

기능 구현은 다 했지만, 추가적으로 필요한 작업이 있습니다.

  • 인증 코드 유효시간
  • 인증 코드 재전송

또한, url클릭 방식이 아닌 코드를 입력받는 방식의 구현도 코드를 조금만 수정하면 가능합니다.

앞으로 업로드 할 글 예정

  • 4번째 글 : 게시글 올리기, 게시글 보기, 게시글 수정
  • 5번째 글 : 내정보 수정
  • 6번째 글 : 관리자

이 글부터는 코드를 제공하지 않습니다. 질문은 트위터멘션으로 부탁드립니다