Notice
Recent Posts
Recent Comments
Link
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
Today
Total
관리 메뉴

9시 24분

Passport.js 본문

Javascript/Node.js

Passport.js

leeeee.yeon 2021. 7. 12. 17:07

앞서 진행했던 코드에 passport.js를 도입해보자. ( 배우기는 쉽지만 활용하기는 어렵대 ....! )

 

 npm install -s passport

 

passport에서는 전략(Strategy)이라는 로그인 방식을 이용해서 oauth를 구현한다.

 

그리고 이번 수업에서는 아이디와 비밀번호를 이용하여 로그인하는 방식을 구현할 것이기 때문에 passport-local도 설치해준다. ( 페이스북 로그인 등을 사용할 시 다른 미들웨어를 설치한다 )

npm install -s passport-local

 

passport는 세션을 내부적으로 사용하기 때문에 session을 활성화하는 코드 아래에 넣어주어야 한다.

const passport = require('passport')
    , LocalStrategy = require('passport-local')
    .Strategy;

 

폼에서 데이터를 전송할 때 login_process로 데이터가 전송된다.

우리는 이 데이터가 전송되는 login_process 부분을 수정하여야 한다.

 

일단 아까 passport 모듈을 불러온 곳 아래에 다음 코드를 추가하자.

app.post('/auth/login_process',
    passport.authenticate('local', {
        successRedirect: '/',
        failureRedirect: '/auth/login'
    }));
  • username과 password를 이용하는 local 방식을 사용
  • 성공했을 땐 홈으로 리다이렉트하고,
  • 실패했을 땐 로그인 화면으로 리다이렉트한다.

그 다음, passport가 어떻게 자격을 확인하는지 ( 아이디, 비밀번호 일치 여부 )에 대해 배워보자.

모듈을 불러온 곳 아래에 다음 코드를 추가하자.

 

passport.use(new LocalStrategy(
    {
        usernameField: 'id',
        // passwordField: 'password'
    },
    function(username, password, done){

    }
));
  • usernameField와 passwordField를 이용하여 디폴트 이름인 'username' & 'passwrod'를 사용자 지정 이름으로 변경할 수 있다.
passport.use(new LocalStrategy(
    {
        usernameField: 'id',
        // passwordField: 'password'
    },
    function(username, password, done){
        if(username === authData.id){
            if(password === authData.password){ // 로그인 성공
                return done(null, authData);
            } else{ // 비밀번호 다름
                return done(null, false, {
                    message: 'Incorrect password'
                });
            }

        } else{ // 아이디 다름
            return done(null, false, {
                message: 'Incorrect username'
            });
        }
    }
));
  • 기존에는 조건문으로 authData와 비교하여 사용자 확인
  • done 메서드에서 false를 주지 않으면 true로 간주

passport 변수를 선언한 곳 아래 다음 코드를 추가해주자.

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

기존에는 우리가 직접 로그인을 구현했다. passport.initialize()와 passport.session() 미들웨어를 사용하여 passport를 통해 세션 미들웨어를 사용할 것이다.

 

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

passport.deserializeUser(function(id, done){
    console.log('deserializeUser', id);
    done(null, authData);
    // User.findById(id, function(err, user){
    //     done(err, user);
    // });
});

serializeUser() - 로그인에 성공했을 때 호출됨 ( 최초 )

우리가 로그인에 성공했을 때 authData를 리턴해주었다. 이 authData가 serializeUser 함수의 첫번째 인자로 전달되고, serializeUser()은 식별자를 세션 스토어에 저장

 

deserializeUser() - 로그인 성공 후 각각의 페이지를 방문할 때마다 콜백 함수를 호출함

데이터가 저장된 곳(authData)에서 데이터를 가져와서 필요한 정보를 조회할 때 사용한다.

 

< lib/auth.js >

module.exports = {
    isLogin: function(request, response){ // 로그인 여부 확인
        if(request.user){
            return true;
        } else{
            return false;
        }
    },
    statusUI: function(request, response){
        var authStatusUI = '<a href="/auth/login">login</a>';
        if(this.isLogin(request, response)){
            authStatusUI = `${request.user.nickname} | <a href="/auth/logout">logout</a>`;
        }
    
        return authStatusUI;
    }
}
  • passport가 request에 user라는 객체를 추가시켜준다. ( deserializeUser 함수에서 authData를 가져와서 )
  • if(request.user) = request에 user 객체가 있다면, undefined는 false와 같음

악 도대체 어디서 잘못된거야 !!!!!!!!!!!! 어디서 잘못된지 모르겠음 ㅠㅠ 영상 쭉 보면서 다시 해봐야할듯 ,,,

>> 해결한듯? 원래 세션이 수정 안되는 에러가 있었다. 저 아래 코드의 resave에 'false'라고 했길래 false로 고쳐준거밖에 특별히 한게 없는데 저거 때문에 에러 수정한건가 ?ㅅ?

app.use(session({
    httpOnly: true,
    secret: 'qwertyasdfg',
    resave: false,
    saveUninitialized: true,
    store: new fileStore()
}));

 

로그아웃 관련 router.get() 함수를 아래와 같이 수정해주자.

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

 

그럼 기본적인 로그인 / 로그아웃 기능을 구현하는데 성공했다!!

근데 로그인을 했을 때 새로고침을 해야지 로그인 UI가 작동한다. 콘솔에 출력을 해보니 로그인 성공 직후에는 request에 user 객체가 undefined하다고 뜬다 흠 어떻게 고치지 ㅠㅅㅠ


pm2 start main.js --watch --no-daemon --ignore-watch="/sessions/*"
  • node가 재시작되어 세션 정보가 저장되지 않는 현상을 방지

교통정리를 위해 main.js, home.js, lib/auth.js, routes/auth.js 파일의 전체 코드를 정리한다.

 

< main.js >

const express = require('express');
const app = express();

// 미들웨어 불러오기
const compression = require('compression');
const helmet = require('helmet');
const session = require('express-session');
const fileStore = require('session-file-store')(session);

// 라우팅 불러오기
const homeRouter = require('./routes/home.js');
const topicRouter = require('./routes/topic.js');
const authorRouter = require('./routes/author.js');
const authRouter = require('./routes/auth.js');

// 미들웨이 사용
app.use(express.urlencoded({extended:false}));
app.use(compression());
app.use(express.static('public'));
app.use(helmet());
app.use(session({
    httpOnly: true,
    secret: 'qwertyasdfg',
    resave: false,
    saveUninitialized: true,
    store: new fileStore()
}));

const authData = {
    id:'leeeeeyeon',
    password: '1',
    nickname: 'leeeeeyeon'
  }

const passport = require('passport')
    , LocalStrategy = require('passport-local')
    .Strategy;

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

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

passport.deserializeUser(function(user, done){
    console.log('deserializeUser', user);
    done(null, authData);
});

passport.use(new LocalStrategy(
    {
        usernameField: 'id'
        // passwordField: 'password'
    },
    function(username, password, done){
        if(username === authData.id){
            if(password === authData.password){ // 로그인 성공
                return done(null, authData);
            } else{ // 비밀번호 다름
                return done(null, false, {
                    message: 'Incorrect password'
                });
            }
        } else{ // 아이디 다름
            return done(null, false, {
                message: 'Incorrect username'
            });
        }
    }
));

app.post('/auth/login_process',
    passport.authenticate('local', {
        successRedirect: '/',
        failureRedirect: '/auth/login'
    }));

// 라우팅 사용
app.use('/', homeRouter);
app.use('/topic', topicRouter);
app.use('/author', authorRouter);
app.use('/auth', authRouter);

// 오류 처리
app.use(function(req, res, next){
    res.status(404).send('404 - Sorry cannot find that!');
 });

app.use(function(err, req, res, next){
    console.error(err.stack);
    res.status(500).send('500 - Something broke!');
});

app.listen(3000);

< home.js >

const express = require('express');
const router = express.Router();

// 파일 분리
const db = require('../lib/db.js');
const template = require('../lib/template.js');
const auth = require('../lib/auth.js');

// 메인 화면
router.get('/', function(request, response){
    console.log('/', request.user);
    db.query(`SELECT * FROM topic`, function(error, topics){
      if(error) throw error;

      var title = 'Welcome';
      var description = 'Hello, Node.js';
      var list = template.list(topics);
      var html = template.HTML(title, list,
        `
        <h2>${title}</h2>${description}
        <img src="/images/squirrel.jpg" style="width: 300px; display: block; margin-top" 10px">
        `,
        `<a href="/topic/create">create</a>`,
        auth.statusUI(request, response)
      );
      response.send(html);
    });
  });

  module.exports = router;

< lib/auth.js >

module.exports = {
    isLogin: function(request, response){ // 로그인 여부 확인
        if(request.user){
            return true;
        } else{
            return false;
        }
    },
    statusUI: function(request, response){
        var authStatusUI = '<a href="/auth/login">login</a>';
        if(this.isLogin(request, response)){
            authStatusUI = `${request.user.nickname} | <a href="/auth/logout">logout</a>`;
        }
    
        return authStatusUI;
    }
}

< routes/auth.js >

const express = require('express');
const router = express.Router();

const sanitizeHtml = require('sanitize-html');

// 파일 분리
const db = require('../lib/db.js');
const template = require('../lib/template.js');

router.get('/login', function(request, response){
  db.query(`SELECT * FROM topic`, function(error, topics){
    db.query(`SELECT * FROM author`, function(error2, authors){

      var title = 'Login';
      var list = template.list(topics);
      var html = template.HTML(sanitizeHtml(title), list,
        `
        <form action="/auth/login_process" method="post">
          <p><input type="text" name="id" placeholder="id"></p>
          <p><input type="password" name="password" placeholder="password"></p>
          <p>
            <input type="submit" value="login">
          </p>
        </form>
      `,
        ``
      );

      response.send(html);
    });
  });
});

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

module.exports = router;

flash message를 이용해서 로그인에 실패했을 때 실패 문구가 뜰 수 있도록 하자.

우선 connct-flash를 설치해야 한다. ( passport가 flash를 이용하는거지, passport에 flash가 내장되어 있지 않음 )

npm install -s connect-flash

 

connect-flash는 세션을 사용하기 때문에 꼭 세션 다음에 미들웨어를 실행해주자.

app.use(flash());

 

app.get('/flash', function(request, response){
    request.flash('info', 'Flash is back!');
    response.redirect('/');
});
app.get('/flash-display', function(request, response){
    response.render('index', {messages: request.flash('info')});
});
  • info에 'Flash is back!'이라는 문구를 기록하여 플래시 메시지가 추가됨

세션에 flash message가 추가된 것을 볼 수 있다. 즉, flash는 세션 스토어에 입력값을 저장한다.

코드를 아래와 같이 수정하고 session을 다시 확인해보자.

app.get('/flash', function(request, response){
    request.flash('msg', 'Flash is back!!');
    response.send('flash');
});

flash() 안에서 값을 수정하면 flash message가 추가되었다.

 

또 코드를 수정해보자.

app.get('/flash-display', function(request, response){
    var fmsg = request.flash();
    console.log(fmsg);
    response.send(fmsg);
});

첫 번째 flash가 없어진 것을 볼 수 있다. 다시 VSC를 들어가면, flash 객체가 비어있는 것을 볼 수 있고, 실제로 reload를 하면 flash가 사라진다.

이를 통해 flash는 일회용성 메세지라는 점을 알 수 있다.

 

여기까지가 flash에 대한 설명이고 이제 passport에 flash를 적용해보자.

app.post('/auth/login_process',
    passport.authenticate('local', {
        successRedirect: '/',
        failureRedirect: '/auth/login',
        failureFlash: true
    }));

틀리게 로그인했을 때 세션에 에러 메세지가 추가된 것을 볼 수 있다.

 

아까 했던 app.use('/flash')랑 app.use('/flash-display')는 지우고, routes/auth.js로 가서 로그인 파트를 아래와 같이 바꾼다.

router.get('/login', function(request, response){
  var fmsg = request.flash();
  var feedback = '';
  if(fmsg.error){
    feedback = fmsg.error[0];
  }
  db.query(`SELECT * FROM topic`, function(error, topics){
    db.query(`SELECT * FROM author`, function(error2, authors){

      var title = 'Login';
      var list = template.list(topics);
      var html = template.HTML(sanitizeHtml(title), list,
        `
        <div style="color:red;">${feedback}</div>
        <form action="/auth/login_process" method="post">
          <p><input type="text" name="id" placeholder="id"></p>
          <p><input type="password" name="password" placeholder="password"></p>
          <p>
            <input type="submit" value="login">
          </p>
        </form>
      `,
        ``
      );

      response.send(html);
    });
  });
});

그럼 이렇게 에러 메시지를 flash message로 띄울 수 있다.

 

그리고 passport.js를 만들어서 passport 관련 코드들을 그 파일에 정리하는 리팩토링도 진행하였다.

 

근데 로그인을 했을 때처럼 새로고침을 해야지 flash message가 작동한다. request에 적용되는 것이 다 조금씩 늦는거 같은데 이를 어떻게 해결해야할까 ?!

+ 리팩토링 이후로 request.user이 undefined로 떠서 로그인이 안됨 !!!

 

>>> main.js에서 코드들의 위치를 바꿔주어서 해결했다.

const express = require('express');
const app = express();

// 미들웨어 불러오기
const compression = require('compression');
const helmet = require('helmet');
const session = require('express-session');
const fileStore = require('session-file-store')(session);
const flash = require('connect-flash');

// 미들웨이 사용
app.use(express.urlencoded({extended:false}));
app.use(compression());
app.use(express.static('public'));
app.use(helmet());
app.use(session({
    httpOnly: true,
    secret: 'qwertyasdfg',
    resave: false,
    saveUninitialized: true,
    store: new fileStore()
}));
app.use(flash());

const passport = require('./lib/passport.js')(app);

// 라우팅 불러오기
const homeRouter = require('./routes/home.js');
const topicRouter = require('./routes/topic.js');
const authorRouter = require('./routes/author.js');
const authRouter = require('./routes/auth.js')(passport);

// 라우팅 사용
app.use('/', homeRouter);
app.use('/topic', topicRouter);
app.use('/author', authorRouter);
app.use('/auth', authRouter);

// 오류 처리
app.use(function(req, res, next){
    res.status(404).send('404 - Sorry cannot find that!');
 });

app.use(function(err, req, res, next){
    console.error(err.stack);
    res.status(500).send('500 - Something broke!');
});

app.listen(3000);
  • passport 변수 선언을 아래로 내리고
  • 라우팅 관련 코드도 아래쪽으로 내렸다.