[Node.js] #10 Passport로 소셜 로그인 구현하기

✨New Wisdom✨·2020년 8월 3일
9

📗 Nodejs 📗

목록 보기
11/20
post-thumbnail

이 노트는 "Nodejs 교과서"를 공부하면서 기록되었다.

Passport

Passport는 Node.js에서 사용하는 인증 미들웨어로, 구글, 페이스북, 카카오톡 등 다양한 로그인 기능을 구현할 수 있게해주는 패키지이다!

🚩 Passport관련 패키지 설치 & app.js

$ npm i passport passport-local passport-kakao bcrypt

패키지를 설치했다면 passport 폴더를 만들고 index.js를 설치하여 모듈을 만들자!

📃 passport/index.js

const local = require('./localStrategy');
const kakao = require('./kakaoStrategy');
const {User} = require('../models');

module.exports = (passport)=>{
    passport.serializeUser((user,done)=>{
        done(null,user.id);
    });
    passport.deserializeUser((id,done)=>{
        User.find({where:{id}})
            .then(user=>done(null,user))
            .catch(err=>done(err))
    });
    local(passport);
    kakao(passport);
}
  • serializeUser
    : req.session 객체에 어떤 데이터를 저장할지 선택한다. done 함수의 첫 번째 인자는 에러 발생 시 사용하며, 두 번째 인자로 user.id만 저장하라고 명령한 것이다. 사용자 정보를 세션에 아이디로 저장하는 것!
  • deserializeUser
    : 매 요청 시 실행된다. passport.session() 미들웨어가 이 메서드를 호출한다. 좀전에 세션에 저장했던 아이디를 받아 데이터베이스에서 사용자 정보를 조회한다. 조회한 정보를 req.user에 저장하므로 앞으로 req.user를 통해 로그인한 사용자의 정보 가져올 수 있다. 세션에 저장한 아이디 통해 사용자 정보 객체 불러오는 것!

app.js에서 모듈을 연결한다!

📃 app.js

...
var passport = require('passport');
const passportConfig = require('./passport');
passportConfig(passport);
...
app.use(session({
  secret:"#JDKLF439jsdlfsjl",
  resave:false,
  saveUninitialized:true,
  store: sessionStore
}))
app.use(passport.initialize());
app.use(passport.session());
  • passport는 내부적으로 session을 사용하기 때문에 미들웨어 장착 순서가 반드시 session 뒤에 존재해야한다!

🚩 local 로그인 구현하기

로그인한 사용자는 회원가입과 로그인 라우터에 접근하면 안되고,
로그인 하지 않은 사용자는 로그아웃 라우터에 접근하면 안된다.
🚨 따라서 라우터에 접근 권한을 제어하는 미들웨어가 필요하다! 🚨

📃 routes/middlewares.js

exports.isLoggedIn =(req,res,next)=>{
    if(req.isAuthenticated()){
        next()
    }else{
        res.status(403).send('로그인 필요');
    }
};

exports.isNotLoggedIn = (req,res,next)=>{
    if(!req.isAuthenticated()){
        next();
    }else{
        res.redirect('/')
    }
}

passport는 req 객체에 isAuthenticated 메서드를 추가한다.
로그인 중이면 req.isAuthenticated()가 true이고 아니면 false이다.
이 미들웨어를 사용해보자!

📃 routes/page.js

const { isLoggedIn, isNotLoggedIn } = require('./middlewares');
...
router.get('/profile',isLoggedIn, (req,res)=>{
  res.render('profile',{title:'내 정보 - NodeBird',user:req.user});
});

router.get('/join',isNotLoggedIn,(req,res)=>{
  res.render('join',{
    title:"회원가입 - NodeBird",
    user:req.user
  });
});

📃 routes/auth.js

const express= require('express');
const passport = require('passport');
const bcrypt = require('bcrypt');
const { isNotLoggedIn, isLoggedIn } = require('./middlewares');
const { User } = require('../models');

const router = express.Router();

// 회원 가입
router.post('/join',isNotLoggedIn, async (req,res,next)=>{
    const {email,nick,password}=req.body;
    try{
        const exUser = await User.find({where:{email}});
        if(exUser){
            console.log('join Error : 이미 가입된 이메일입니다.');
            return res.redirect('/join');
        }
        const hash = await bcrypt.hash(password,12);
        await User.create({
            email,
            nick,
            password:hash,
        });
        return res.redirect('/');
    }catch(error){
        console.log(error);
        return next(error);
    }
});

// 로그인
router.post('/login',isNotLoggedIn,(req,res,next)=>{
    passport.authenticate('local',(authError,user,info)=>{
        if(authError){
            console.error(authError);
            return next(authError);
        }
        if(!user){
            console.log('login Error');
            return res.redirect('/');
        }
        return req.login(user,(loginError)=>{
            if(loginError){
                console.error(loginError);
                return next(loginError)
            }
            return res.redirect('/');
        });
    })(req,res,next); // 미들웨어 내의 미들웨어에는 (req,res,next)를 붙인다.
});

//로그아웃
router.get('/logout',isLoggedIn,(req,res)=>{
    req.logout();
    req.session.destroy();
    res.redirect('/');
})

module.exports =router;
  • 로그인 라우터
    : passport.authenticate('local') 미들웨어가 로컬 로그인 전략을 수행한다. 미들웨어 안에 미들웨어가 있는 형식인데, 미들웨어에 사용자 정의 기능을 추가하고 싶으면 내부 미들웨어에 (req, res, next)를 인자로 제공해서 호출하면 된다.
  • 로그아웃 라우터
    : req.logout 메서드는 req.user 객체를 제거하고, req.session.destroy는 session 객체의 내용을 제거한다.

📃 routes/localStrategy.js

const LocalStrategy = require('poassport-local').Strategy;
const bcrypt = require('bcrypt');
const passport = require('passport');
const { User } = require('../models');

module.exports=(passport)=>{
    passport.use(new LocalStrategy({
        usernameField :'email',
        passwordFiled :'password',
    }, async(email,password,done)=>{
        try{
            const exUser = await User.find({where:{email}});
            if(exUser){
                const result = await bcrypt.compare(password,rxUser,password);
                if(result){
                    done(null,exUser);
                } else{
                    done(null,false,{message:'비밀번호가 일치하지 않습니다.'});
                }
            } else{
                done(null,false,{message:'가입되지 않은 회원입니다.'});
            }
        } catch(error){
            console.error(error);
            done(error);
        }
    }));
};

passport-local 모듈에서 strategy 생성자를 불러와 사용한다.

strategy?
!어떤 로그인 방식을 취하냐!


🚩 kakao 로그인 구현

sns 로그인은 처음 로그인 할 때는 회원가입 처리, 다음 로그인 부터는 로그인 처리를 해주어야 한다.

📃 passport/kakaoStrategy.js

const passport = require('passport');
const { User } = require('../models');

const KakaoStrategy = require('passport-kakao').Strategy;

module.exports=(passport)=>{
    passport.use(new KakaoStrategy({
        clientID:process.env.KAKAO_ID,
        callbackURL:'auth/kakao/callback',
    }, async (accessToken,refreshToken,profile,done)=>{
        try{
            const exUser = await User.find({where:{snsId:profile.id,provider:'kakao'}});
            if(exUser){
                done(null,exUser);
            } else{
                const newUser = await User.create({
                    email:profile._json && profile._json.kaccount_email,
                    nick:profile.displayName,
                    snsId:profile.id,
                    provider:'kakao',
                });
                done(null,newUser);
            }
        }catch(error){
            console.error(error);
            done(error);
        }
    }));
};
  • clientId : 카카오에서 발급해주는 아이디. 노출되면 안되니 나중에 아이디 발급받아 env 파일에 넣을거다.

📃 routes/auth.js - 카카오 로그인 라우터

router.get('kakao',passport.authenticate('kakao'));
router.get('/kakao/callback',passport.authenticate('kakao',{
    failureRedirect : '/'
}),(req,res)=>{
    res.redirect('/');
});
  • GET '/auth/kakao'
    : 여기로 접근하면 로그인 과정이 실행된다. 로그인 창으로 리다이렉트를 하고 결과를 GET 'auth/kakao/callback'으로 받는다.
  • passport.authenticate 메서드에 콜백 함수를 제공하지 않는다. 카카오 로그인은 내부적으로 req.login을 호출하므로 직접 호출할 필요가 없다.

clientId 발급 받기

카카오 개발자 사이트에 들어가서 앱 하나 만든다!
https://developers.kakao.com/
그리고 RESTAPI키를 env 파일에 입력해준다!

profile
🚛 블로그 이사합니다 https://newwisdom.tistory.com/

1개의 댓글

comment-user-thumbnail
2020년 8월 4일

안녕하세요. 기획자입니다.
노드JS로 회사에서 사이트를 개발하는데 작성하신 소셜로그인 관련 문의사항이 있습니다.
회원가입 시 카카오나 네이버에 로그인이 되어있는 상태라면 다시 해당 ID,PW를 입력하지 않고
자동으로 끌고와 회원가입절차가 진행되지 않는건가요? www.onoffmix.com 이라는 사이트의 경우
그 기능이 되어 있어 개발자에게 요청하니 노드js는 그 기능이 안된다... 라고 하여 답답하네요..

답글 달기