본문 바로가기

Dev.BackEnd/Node.js

[열두번째] Node.js Authentication / Passport JS


(포스팅에 앞서 이 포스팅은 생활코딩 강의를 듣고 만든 포스팅임을 밝힙니다.)


웹 사이트를 만드는데 있어서 가장 중요한 부분인 '인증'에 대해서 포스팅 하고자 한다.

궁극적으로는 Facebook을 사용하여 로그인을 하는 방법을 구현하였다.

Federation Authentication 이라고 부른다.

요즘 대부분의 웹 사이트에서는 사용자의 정보를 갖고 있는 것이 적잖이 부담스러워 이 방식을 사용한다.

이를 구현하기 위해 passport 모듈을 사용하였다.


사용자의 패스워드를 암호화하여 저장하는 방식에는 md5, sha256 등 많은 모듈이 있지만

그 하나 하나를 다루지 않고 passport 모듈을 이용한 방법을 소개하고자 한다.

passport 모듈을 사용하면 여러 인증방식을 통합할 수 있다.

결국엔 session 을 생성하여 사용자의 정보를 저장하는 것이다.

데이터베이스에 접목하기 전에 배열을 통해서 사용자의 정보를 저장하는 방식을 사용하겠다.

코드는 완성본을 잘라서 올렸다.


code 1)

var express = require('express');
var session = require('express-session');
var FileStore = require('session-file-store')(session);
var bodyParser = require('body-parser');
var bkfd2Password = require("pbkdf2-password");
var passport = require('passport')
var LocalStrategy = require('passport-local').Strategy;
var FacebookStrategy = require('passport-facebook').Strategy;
var hasher = bkfd2Password();
var app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({
  secret: '1305856c87d3999247c92a993a20e836',
  resave: false,
  saveUninitialized: true,
  store:new FileStore()
}));
app.use(passport.initialize());
app.use(passport.session());


=

각 모듈을 불러오고 Setting 하는 코드의 초반부이다.

express , session, FileStore 등 눈에 익은 모듈들이 보인다.

passport를 불러왔고

LocalStrategy 와 FacabookStrategy 이 두 모듈을 함께 요청했다.

전자는 웹 사이트 자체에서 인증을 하는 방식이고

후자는 페이스북 아이디를 통해 인증을 하는 방식이다.

bkfd2Password 는 hash를 생성해주는 모듈이다.

위에서 설명한 md5, sha256 보다 한 단계 발전된 방식이다.




code 2)

app.get('/auth/logout', function(req,res){
  req.logout();
  req.session.save(function(){
    res.redirect('/welcome');
  });
});
app.get('/welcome',function(req,res){
  if(req.user && req.user.displayName){
    res.send(`
      <h1>Hello, ${req.user.displayName}</h1>
      <a href="/auth/logout">logout</a>
      `);
  } else {
    res.send(`
      <h1>Welcome</h1>
      <ul>
        <li><a href="/auth/login">Login</a></li>
        <li><a href="/auth/register">Register</a></li>
      </ul>
      `);
  }
});


=

어쩌다 보니 logout 페이지가 맨 위로 올라오게 되었다.

passport 모듈이 가지고 있는 logout 메서드를 이용한다.

그리고 /welcome 페이지로 redirect 해준다.


welcome 페이지로 get 요청

Jade 템플릿 엔진을 사용하지 않고 ` ` 을 사용해 node 코드 위에 View 단을 구성했다.

user 값이 존재하고 displayName 값이 존재하는 경우에 로그인이 되도록 했다.

존재하지 않으면 재 로그인이 가능하도록 구성했다.




code 3)

app.get('/auth/login',function(req,res){
  var output = `
  <h1> Login </h1>
  <form action="/auth/login" method="post">
    <p>
      <input type = "text" name="username" placeholder="username">
    </p>
    <p>
      <input type = "password" name="password" placeholder="password">
    </p>
    <p>
      <input type="submit">
    </p>
  </form>
  <a href="/auth/facebook">FACEBOOK</a>
  `;
  res.send(output)
});


=

로그인 화면 get 요청

code 4,5,6이 passport 메인 코드이다.




code 4)

passport.serializeUser(function(user, done) {
  console.log('serializeUser',user);
  done(null, user.authId);
});
passport.deserializeUser(function(id, done) {
console.log('deserializeUser', id);
  for(var i=0; i<users.length; i++){
    var user = users[i];
    if(user.authId === id){
      return done(null, user);
    }
  }
  done('There is no user');
});


=

이미 등록된 user 와 새로 등록되어 로그인 된 user의 경우를 나누어서 처리한다.

done 이라는 녀석을 쓰게 되는데 정말 함축적인 의미로 사용되고 같은 모습으로 다른 일을 하기에 헷갈리기 쉽다.

이미 등록된 user는 바로 done을 실행시키면 된다. (console은 콘솔창에서 유저에 대한 정보를 확인하기 위함이다.)

새로 등록하여 로그인하는 사용자에게는 deserializeUser가 적용된다.

배열에서 일치하는 사용자가 있는지 확인하고 없으면 콜백함수를 실행한다.




code 5)

passport.use(new LocalStrategy(
  function(username, password, done){
    var uname = username;
    var pwd = password;
    for(var i=0; i<users.length; i++){
      var user = users[i];
      if(uname === user.username){
        return hasher({password:pwd, salt:user.salt}, function(err,pass,salt,hash){
          if(hash === user.password){
            done(null, user);
          } else {
            done(null, false);
          }
        });
      }
    }
    done(null,false);
   }
));


=

local login 방식에 대한 코드이다.




code 6)

passport.use(new FacebookStrategy({
    clientID: '1718836971727460',
    clientSecret: 'ec61fd56494c15cf445ba4dd5b16ae2e',
    callbackURL: "/auth/facebook/callback",
    profileFields:['id','email','displayName']
  },
  function(accessToken, refreshToken, profile, done) {
    var authId = 'facebook:'+profile.id;
    for(var i=0;i<users.length;i++){
      var user = users[i];
      if(user.authId === authId){
        return done(null,user);
      }
    }
    var newuser = {
      'authId':authId,
      'displayName':profile.displayName
      'email':profile.emails[0].value
    };
    users.push(newuser);
    done(null,newuser);
  }
));


=

facebook login 방식에 대한 코드이다.

Facebook for Developer 페이지에서 ID App Secret 을 가져와야 한다.


local과 차이점은 facebook 인증을 추가한다는 것이다.


code 7)

//local login
app.post(
  '/auth/login',
  passport.authenticate(
    'local',
    {
      successRedirect: '/welcome',
      failureRedirect: '/auth/login',
      failureFlash: false
    }
  )
);
//facebook login
app.get(
  '/auth/facebook',
  passport.authenticate(
    'facebook',
    {scope: 'email'}
  )
);
app.get(
  '/auth/facebook/callback',
  passport.authenticate(
    'facebook',
    {
      successRedirect: '/welcome',
      failureRedirect: '/auth/login'
    }
  )
);


=

local login 방식이나 facebook login 방식이나 둘 다 성공했을 경우와 실패했을 경우에 나누어서 설정을 해준다.

successRedriect

failureRedirect

두 가지 경우에 대해서 보여줄 화면을 설정한다.

failureFlash 는 로그인이 되지 않았을 경우 보여줄 팝업창을 설정하는 것이라고 보면 된다.

false로 하면 따로 팝업을 하지 않는다는 의미이다.


페이스북 로그인 시 두 가지의 라우터를 설정해주는 이유는 보안을 위해서이다.

서버에 요청을 할 때 한번 인증을 하고 다시 요청을 보낼 때 인증을 한다.

두 번 타는 보일러 처럼 인증을 한 번 더 해주기 때문에 보다 안정적이다.




code 8)

app.post('/auth/register',function(req,res){
  hasher({password:req.body.password}, function(err,pass,salt,hash){
    var user = {
      authId:'local:'+req.body.username,
      username:req.body.username,
      password:hash,
      salt:salt,
      displayName:req.body.displayName
    };
    users.push(user);
    req.login(user, function(err){
      req.session.save(function(){
        res.redirect('/welcome');
      });
    });
  });
});


=

register 페이지에서 사용자가 정보를 입력했을 경우 post로 요청한 코드이다.

사용자가 입력한 password 정보를 hasher로 암호화해준다.

콜백 함수는 인자로 err, pass, salt, hash 값을 받는다.

받은 값을 각각 저장해준다.

그리고 users 배열에 push 해준다. 배열 마지막에 추가한다는 의미이다.

login method를 이용하고 session 을 저장한다.

입력이 완료되면 welcome 페이지로  redirecting 해준다.




code 9)

var users = [
  {
    authId:'local:ljyhanl',
    username:'ljyhanl',
    password:'kJvMbQu1YPsLyccfvLDdpOFW2tyE9qBdicnnKR//YbfF6BiAc6JsCm5mp/VHiB/EvJC7UVPn0shcb9vJh70pmtHT+UABLT52vbw9DOF+hVH7r2qBe4Y5kZScf3LxlEkgfJxXt45WkCMFXEtL73WCGqelNFUPmHlVQhr/fy2wN8A=',
    salt:'7zy7QTSug3313uHCHNPGqlua8g0WEOeR51XZlj/BisIAznwcfchUGic1SaouwD3gZDiBpv36pbQaJM/r6ZJbmQ==',
    displayName:'Camomile'
  }
];
app.get('/auth/register',function(req,res){
  var output = `
  <h1> Register </h2>
  <form action="/auth/register" method="post">
    <p>
      <input type = "text" name="username" placeholder="username">
    </p>
    <p>
      <input type = "password" name="password" placeholder="password">
    </p>
    <p>
      <input type = "text" name="displayName" placeholder="displayName">
    </p>
    <p>
      <input type="submit">
    </p>
  </form>
  `
  res.send(output);
})


=

사용자가 입력한 정보를 users라는 배열에 저장한다.

이미 배열에 저장되어 있는 값은 dafault 정보이다.

배열에서 [] 로 묶어주는 것을 잊으면 안된다.

자바스크립트 문법이니 알고 있을거라 생각한다.


register 페이지 form이다.



code 10)

app.listen(3003, function(){
  console.log('connected, 3003 Port!');
});


=

익숙한 코드이니 부차적인 설명은 생략한다.




(생활코딩 감사합니다.)

-..-