본문 바로가기
프로그래밍 개발/Express

Express - 페이스북 로그인 구현

by Jinseok Kim 2021. 1. 20.
반응형

 

페이스북 로그인 구현

 

  • 회원정보를 보관하는 것은 회원입장에선 불편하고, 서비스 입장에선 부담되는 일입니다. 이런 문제를 해결하기 위해서 최근에는 페이스북이나 구글과 같은 기업들이 로그인 연동 기능을 제공한다.
  • 이를 Federated Identity라고 하고 Passport.js를 이용하면 이를 쉽게 구현할 수 있다.

 

 

 

npm install -s passport-facebook

위의 코드를 입력하여 페이스북 로그인 구현을 위한 모듈을 다운로드한다.

 

 

 

 

 

var = FacebookStrategy = require('passport-facebook').Strategy;



passport.use(new FacebookStrategy({
        clientID: FACEBOOK_APP_ID,
        clientSecret: FACEBOOK_APP_SECRET,
        callbackURL: "http://www.example.com/auth/facebook/callback"
      },
      function(accessToken, refreshToken, profile, done) {
        // User.findOrCreate(..., function(err, user) {
        //   if (err) { return done(err); }
        //   done(null, user);
        // });
      }
    ));

그리고 위 두 코드를 passport을 구현시키는 코드 파일에 붙여줘야한다.

 

 

 

 

 

https://developers.facebook.com/

 

Facebook for Developers

Facebook for Developers와 사용자를 연결할 수 있는 코드 인공 지능, 비즈니스 도구, 게임, 오픈 소스, 게시, 소셜 하드웨어, 소셜 통합, 가상 현실 등 다양한 주제를 둘러보세요. Facebook의 글로벌 개발

developers.facebook.com

그 다음으로 페이스북 개발자 사이트에 들어가서 앱을 생성해야한다.

 

 

 

 

 

 

개발자 사이트에 들어가 로그인을 한 후 내 앱 메뉴를 클릭한다.

 

 

 

 

 

 

 

개개인의 정보들을 인증 한 후에 넘어간다.

 

 

 

 

 

 

 

인증 후에 앱 만들기를 클릭하면 앱 만들기를 할 수 있다.

 

 

 

 

 

 

 

로그인 기능을 설정하기 위해서 페이스북 로그인 설정하기를 클릭하고 웹 플렛폼 선택 후 임의의 URL을 적어주고 설정을 마친다.

 

 

 

 

 

 

 

왼쪽 메뉴에 기본 설정을 클릭해보면 페이스북 로그인 기능을 위해 사용될 '앱 시크릿 코드'를 받을 수 있다.

 

 

 

 

 

 

config/facebook.json에 심어둔 callbackURL을 페이스북 개발자 사이트 설정에서 위와 같이 리디렉션 URL에 붙여주고 저장해주어야 한다.

 

 

 

 

config/facebook.json

{
        "clientID": "1440599276283262",
        "clientSecret": 시크릿 코드,
        "callbackURL": "http://localhost:3000/auth/facebook/callback"
      }

 

 

lib/auth.js

module.exports = {
    isOwner: function(request, response){
    if(request.user){
        return true;
    } else{
        return false;
    }
}, statusUI: function(request, response){
    var authStatusUI = `
            <a href="/auth/login">login</a> | 
            <a href="/auth/register">Register</a> |
            <a href="/auth/facebook">Login with Facebook</a>` //페이스북 로그인 UI 추가
    if(this.isOwner(request, response)){
        authStatusUI = `${request.user.displayName} | <a href="/auth/logout">logout</a>`;
    }
    return authStatusUI;
}


}

 

 

 

lib/passport.js

var db = require('../lib/db');
var bcrypt = require('bcrypt');
var shortid = require('shortid');

module.exports = function (app) {


    var passport = require('passport'),
        LocalStrategy = require('passport-local').Strategy,
        FacebookStrategy = require('passport-facebook').Strategy; //페이스북 passport 기능 연동

    app.use(passport.initialize());
    app.use(passport.session());

    passport.serializeUser(function (user, done) {
        done(null, user.id);
    });

    passport.deserializeUser(function (id, done) {
        var user = db.get('users').find({id:id}).value();
        done(null, user);
    });

    passport.use(new LocalStrategy({
            usernameField: 'email',
            passwordField: 'pwd'
        },
        function (email, password, done) {
        var user = db.get('users').find({
            email:email
            
        }).value();
           if(user){
                    bcrypt.compare(password, user.password, function(err,result){
                    if(result){
                        return done(null, user, {
                            message: 'Welcome.'
                        });
                    } else {
                        return done(null, false, {
                            message: 'Password is not correct.'
                        });
                    }
                    });
               }
                 else {
                    return done(null, false, {
                        message: 'There is no email.'
                    });
                }
            
            }
    ));
    var facebookCredentials = require('../config/facebook.json'); //facebook.json과 연동
                               //facebook.json에 배열 안에 있는 값들을 생성
    facebookCredentials.profileFields = ['id', 'emails', 'name', 'displayName'];
    passport.use(new FacebookStrategy(facebookCredentials,
    //아래 콜백함수가 페이스북 로그인을 구현되게끔하는 많은 과정을 함축해서 처리해준다.
      function(accessToken, refreshToken, profile, done) {
         console.log('FacebookStrategy', accessToken, refreshToken, profile);
         //profile의 프로퍼티 중 emails의 첫번째 값이 페이스북 회원 email 값을 갖고 있음.
        var email = profile.emails[0].value;
        
        //페이스북 회원 email 값을 받은 변수 email을 사용하여 db.json에서 동일한
        //이메일 정보를 찾는 값을 변수화한다.
            var user = db.get('users').find({email:email}).value();
            
       //페이스북 회원 이메일 값이 이미 user에 존재한다면 if문이 발동되어 db.json 안에 삽입된
       //user.facebookId와 원래 있었던 보통 회원 정보인 profile.id와 일치하도록 하고
       //db.json의 users에 email 값을 삽입한다. 즉 보통 회원 이메일과 페이스북 
       // 이메일과 중복되면 합쳐지도록 하여 하나의 같은 회원 정보를 갖게 된다.
            if(user){
             user.facebookId = profile.id;
             db.get('users').find({email:email}).assign(user).write();
            } 
            
            //이메일이 중복되지 않으면 아래와 같이 새로운 user 정보를 db.json에 삽입한다.
            else {

                user = {
                    id:shortid.generate(),
                    email:email,
                    displayName:profile.displayName,
                    facebookId:profile.id
                }
                db.get('users').push(user).write();
            }
            done(null, user);
        // User.findOrCreate(..., function(err, user) {
        //   if (err) { return done(err); }
        //   done(null, user);
        // });
      }
    ));
    
    //아래 코드는 facebook에서 제공하는 코드로서 페이스북 인증 로그인이 발동될때 자동으로
    //페이스북으로 이동할 주소를 만들어주는 코드로 보면 된다.
     app.get('/auth/facebook', passport.authenticate('facebook', {
        scope:'email' //email 정보를 주어 사용자를 가린다.
    }));
    
    //아래 코드도 마찬가지로 페이스북 로그인 발동시 설정해준 callback URL이 발동되면 아래 코드
    //가 실행되어 매우 복잡한 로그인 인증 과정을 처리해준다.
    app.get('/auth/facebook/callback',
        passport.authenticate('facebook', {
            successRedirect: '/',
            failureRedirect: '/auth/login'
        }));
    return passport;
}

 

 

 

routes/auth.js

var express = require('express');
var router = express.Router();
var path = require('path');
var fs = require('fs');
var sanitizeHtml = require('sanitize-html');
var template = require('../lib/template.js');
var shortid = require('shortid');
var db = require('../lib/db');
var bcrypt = require('bcrypt');


module.exports = function (passport) {
  router.get('/login', function (request, response) {
    var fmsg = request.flash();
    var feedback = '';
    if (fmsg.error) {
      feedback = fmsg.error[0];
    }
    var title = 'WEB - login';
    var list = template.list(request.list);
    var html = template.HTML(title, list, `
      <div style="color:red;">${feedback}</div>
      <form action="/auth/login_process" method="post">
        <p><input type="text" name="email" placeholder="email"></p>
        <p><input type="password" name="pwd" placeholder="password"></p>
        <p>
          <input type="submit" value="login">
        </p>
      </form>
    `, '');
    response.send(html);
  });

  router.post('/login_process',
    passport.authenticate('local', {
      successRedirect: '/',
      failureRedirect: '/auth/login',
      failureFlash: true,
      successFlash: true
    }));
    
    router.get('/register', function (request, response) {
    var fmsg = request.flash();
    var feedback = '';
    if (fmsg.error) {
      feedback = fmsg.error[0];
    }
    var title = 'WEB - login';
    var list = template.list(request.list);
    var html = template.HTML(title, list, `
        <div style="color:red;">${feedback}</div>
        <form action="/auth/register_process" method="post">
          <p><input type="text" name="email" placeholder="email"></p>
          <p><input type="password" name="pwd" placeholder="password"></p>
          <p><input type="password" name="pwd2" placeholder="password"></p>
          <p><input type="text" name="displayName" placeholder="display name"></p>
          <p>
            <input type="submit" value="register">
          </p>
        </form>
      `, '');
    response.send(html);
  });
    
router.post('/register_process', function (request, response) {
    var post = request.body;
    var email = post.email;
    var pwd = post.pwd;
    var pwd2 = post.pwd2;
    var displayName = post.displayName;
   if(pwd !== pwd2){
      request.flash('error', 'Password must same!');
      response.redirect('/auth/register');
    } else {
       bcrypt.hash(pwd, 10, function (err, hash) {
       
     //일단 회원가입 폼에서 받아온 회원가입 email 값을 통해 db.json에서
     //users에 같은 email 값을 가지고 있는 user을 찾는 값을 변수화 한다.
        var puser = db.get('users').find({email:email}).value();
        
     //이때 email값이 똑같은 값이 존재한다면 if문이 발동하여 아래와 같이 
     //puser의 프로퍼티 password, displayName에 동일했던 이메일의 패스워드와 displayNam 값을 붙여주기만한다.
        if(puser){
          puser.password = hash;
          puser.displayName = displayName;
          
      //여기가 중요한게 id가 puser.id인 값을 찾아 puser을 준다는 것은 중복된 데이터 생성을 막는거다.
      //즉 이미 페이스북 로그인 정보를 받았다면 똑같은 페이스북 이메일로 보통 회원가입을 하면 
      //그 보통 회원가입한 user 정보 안에 페이스북 id만 추가된다.
          db.get('users').find({id:puser.id}).assign(puser).write();
          
          
        //이메일 중복이 없어 puser값이 없다면 else문이 발동되어
        //아예 새로 통째로 user 데이터를 만들어준다.
        } else {
          var user = {
            id: shortid.generate(),
            email: email,
            password: hash,
            displayName: displayName
          };
          db.get('users').push(user).write();
        }
        request.login(user, function (err) {
          console.log('redirect');
          return response.redirect('/');
        })
      });
      
    }
  });    

  router.get('/logout', function (request, response) {
    request.logout();
    request.session.save(function () {
      response.redirect('/');
    });
});

  return router;
} 

 

 

 

페이스북과 연동되며 동시에 lowdb의 db.json 파일에 페이스북의 회원정보도 들어오게 되었다.

 

또 만약 페이스북으로 로그인 한 후 페이스북 로그인이 아닌 보통의 회원가입으로 로그인 했었던 페이스북 이메일과 동일한 이메일을 통해 가입했다면 동일한 user 정보를 쓰게끔 구현하였다. 

 

 

 

 

 

 

 

반응형

댓글