Passport 모듈로 로그인 😋

박희수·2024년 3월 4일
0
post-thumbnail

🤔 Passport란 ?

회원가입과 로그인 같은 복잡한 작업이 많은 기능을 쉽게 구현할 수 있게 도와주는 검증된 모듈이다.
passport 문법은 이해하면 쉽지만, 이해하기까지의 과정은 복잡하고 난해하기 때문에 이 과정을 기록하려 한다.


1. 패스포트 설치하기 🤸‍♂️

npm i passport passort-local passport-kakao bcrypt 

-> 비밀번호 암호화를 도와주는 bcrypt도 같이 설치한다.
-> 소셜 네트워크로 로그인도 가능하기 때문에, kakao도 함께 설치한다.

2. passport 폴더 생성 📂

-> 루트에 passport라는 폴더 생성 후, 전략 파일들을 넣어줄 것이다.
-> 전략 파일이란, 로그인 인증 전략을 말하는데 우리는 local과 kakao를 설치했으니 localStrategy.js 파일과 kakaoStrategy.js 파일을 생성한다.
-> index.js파일도 생성

3. app.js와 연결 ✨

// app.js

const passport = require('passport');

const passportConfig = require('./passport');

const app = express();
passportConfig(); //🔑 passport 설정 

app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session({
	resave : false,
    saveUninitialized : false,
    secret : process.env.COOKIE_SECRET,
    cookie : {
    	httpOnly : true,
        secure : false,
    },
}));

app.use(passport.initialize()); // 🔑 요청 객체에 passport 설정을 심음
app.use(passport.session()); //🔑 req.session객체에 passport 정보 저장 
-> passport.session()이 실행되면, 세션 쿠키 정보를 바탕으로 해서 passport/index.js의 deserializeUser()가 실행 , deserailizeUser는 추후 만들 예정. 

4. passport/index.js 작성

// passport/index.js

const passport = require('passport');
const local = require('./lcoalStrategy');
const kakao = require('./kakaoStrategy');
const User = require('../models/user');

module.exports = () => {
	passport.serializeUser((user, done) => {
    	done(null, user.id);
    }); 
    -> 🔑 로그인이 성공했을 때 실행될 함수 
    -> req.session 객체에 어떤 데이터를 저장할 지 선택, 사용자의 아이디만 저장 (메모리 차지때문) 
    
    passport.deserializeUser((id, done) => {
    	User.findOne({ where : { id })
        	.then(user => done(ull, use))
            .catch(err => done(err));
    });
    -> req.session에 저장된 사용자 아이디를 바탕으로 db 조회로 사용자 정보를 얻어낸 후
    -> req.user에 저장 
    -> 사용자가 사이트에 접속할 때마다 호출되는 함수 
    
    local();
    kakao();
}

🔺 passport 처리과정

본격적으로 로그인,회원가입 라우트를 작성하기 전, passport 처리과정을 정리하고자 한다.

로그인 과정
1. 로그인 요청이 들어옴 (routes/auth)
2. passport.authenticate 메서드 호출 (전략 파일 실행)
3. 로그인 전략 수행 (2번에서 생성한 localStrategy, kakaoStrategy 작성)
4. 로그인 성공 시 사용자 정보 객체와 함께, req.login 호출
5. req.login 메서드가 passport.serializeUser 호출 (passport/index.js의 함수 serailizeUser)
6. req.session에 사용자 아이디만 저장 ( { 세션쿠키 : 유저id } 형태로 메모리에 저장)
7. 로그인 완료

🐾 로그인 이후 과정 🐾
1. 모든 요청에 passport.session() 미들웨어가 passport.deserializeUser메서드 호출 (passport/index.js의 함수 deserializeUser)
2. req.session에 저장된 아이디로 데이터베이스에서 사용자 조회
3. 조회된 사용자 정보를 req.user에 저장
4. 라우터에서 req.user 객체 사용 가능


회원가입 라우터 작성하기 🎅

1. routes/auth.js 작성

const express = require('express');
const passport = require('passport');
const { isNotLoggedIn } = require('../middlewares');
const { join } = require('../controllers/auth');

const router = express.Router();

router.post('/join', isNotLoggedIn, join);

module.exports = router;

⭐ route.post('/join', isNotLoggedIn, join); 코드 설명
-> /join : 라우트 이동 path
-> isNotLoggedIn : 로그인 여부 확인 middleware
-> join : 실질적으로 회원가입하는 로직을 보여주는 controller

2. 로그인 여부 미들웨어 작성

middlewares 폴더 생성 -> index.js 파일 생성

// middlewares/index.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 {
    	const message = encodeURIComponent('로그인한 상태입니다');
        res.redirect(`/?error=${message}`);
        res.status(403).send('로그인 필요');
    }
};

3. 회원가입 로직 controller 작성

controllers 폴더 생성 -> auth.js 파일 생성

// controllers/auth.js

const passport = require('passport');

const User = require('../models/user');
const bcrypt = require('bcrypt');

exports.join = async (req, res, next) => {
	const { nick, email, password } = req.body;
    try {
    	const exUser = await User.findOne({ where : { email }});
        if(exUser) {
        	return res.redirect('/join?error=exist');
        } // 이미 존재하는 이메일이면 리다이렉팅
        const hash = await bcrypt.hash(password,12); // 🔑암호화
        await User.create({
        	email,
            nick,
            password : hash
        })
        return res.redirect('/');
   } catch (error) {
   		console.log(error);
        next(error);
	}
}

4. app.js에 라우트 연결

const authRouter = require('../routes/auth');

app.use('/auth', authRouter);

-> 위 두 코드 추가해주면, '/auth'로 회원가입 라우터에 접근 가능 

로그인 라우터 작성하기 🙋‍♀️

1. router/auth.js 작성

-> 기존의 auth.js의 코드에 로그인 관련 코드만 추가해줄 것임.

const express = require('express');
const passport = require('passport');
const { isNotLoggedIn } = require('../middlewares');
const { join , login} = require('../controllers/auth'); // 로그인 컨트롤러도 추가로 가져오기 

const router = express.Router();

router.post('/join', isNotLoggedIn, join);
router.post('/login', isNotLoggedIn, login); // 로그인 라우터 추가, 미들웨어는 동일 

module.exports = router;

-> isNotLoggedIn 미들웨어 작성은 이미했으니, 생략

2. 로그인 로직 controller 작성

// controllers/auth.js

exports.login = async (req, res, next) => {
	passport.authenticate('local', (authError, user, info) => {
    	if(authError) {
        	console.error(authError);
            return next(authError);
        } // 서버 실패시 
       	if(!user) {
        	return res.redirect(`/?loginError=${info.message}`);
        } // 유저 없을 때 (로직 실패)
        return req.login(user, (loginError) => {
        	if(loginError) {
            	console.log(loginError);
                return next(loginError);
            }
            return res.redirect('/');
        }) // 인증이 성공하면, req.login으로 세션에 유저 정보 저장 
       
      })(req, res, next) // 미들웨어 확장 패턴 
};

2번 코드 해석
🔺 passport.authenticat('local', ...)
-> 이 부분이 전략을 말함. passport의 특성인데 이 코드에서 먼저 localStrategy 파일로 이동해 전략 과정을 거친다. localStrategy는 곧 작성할 예정.
🔺 (authError, user, info)
-> localStrategy에 작성될 done메소드에 들어가는 파라미터와 동일한 역할을 한다고 볼 수 있음. -> done(null, false, {message : '비밀번호 불일치' } ) 이렇게 결과를 내려주면 이를 각각 authError, user, info로 해석할 수 있음
⭐ req.login(user, ..)
-> 전략(localStrategy) 코드에서 성공해서 done(null, exUser)이런 식으로 결과를 내려주면 실행되는 로그인 코드. req.login으로 세션에 유저 정보를 저장한다. 그리고, req.login 메서드가 passport.serializeUser 호출 (passport/index.js의 함수) -> serailizeUser가 req.session에 사용자 아이디만 저장해줌 -> 로그인 완료 ⭐

3. localStrategy.js 작성 (전략 작성)

const passport = require('passport');
const User = require('../models/user');
const { Strategy : LocalStrategy } = require('passport-local').Strategy;
// -> LocalStrategy라는 이름으로 Strategy 생성을 할 수 있음. 

const bcrypt = require('bcrypt');

module.exports = () => {
	passport.use(new LocalStrategy({
    	usernameField : 'email', // req.body.email
        passwordField : 'password', // req.body.password
        passReqToCallback : false,
   }, async (email, password, done) => { 
     try {
     	const exUser = await User.findOne({ where : { email }});
        if(exUser) { // 유저가 있으면
        	const result = await bcrypt.compare(password, exUser.password) 
            // -> 저장된 유저의 비밀번호와 사용자가 입력한 비밀번호가 일치하는 지 비교 
            if(result) {
            	done(null, exUser);
                console.log(exUser, 'user존재')
                // 일치한다면 exUser 보내주기 
           } else {
           		done(null, false, { message : '비밀번호가 일치하지 않습니다.'})
           } 
       } else {
       		done(null, false, { message : '가입되지 않은 회원입니다.'});
       }
    } catch (error) {
    	console.error(error);
        done(error);
    }
  }))
};
    

-> app.js에서 이미 auth로 로그인/회원가입 라우터 등록했으니, 그 과정은 생략


카카오 로그인 구현해보기 🎃

1. kakaoStrategy.js 작성

// pasport.kakaoStrategy.js

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

const User = require('../models/user');

module.exports = () => {
	passport.use(new KakaoStrategy({
    	clientID : process.env.KAKAO_ID, // ✨ 카카오 앱 아이디 추가 
        callbackURL : '/auth/kakao/callback', // 🎈 카카오 로그인 후 카카오가 결과를 전송해줄 url
   }, async (accessToken, refreshTokeny, profile, done) => {
   	console.log('kakao profile', profile); 
    try {
    	const exUser = await User.findOne({
        	where : { snsId : profile.id, provider : 'kakao'} ,
    });
    if (exUser) {
    	done(null, exUser);
    } else {
    	const newUser = await User.create({
        	email : profile._json?.kaka_account?.email,
            nick : profile.displayName,
            snsId : profile.id,
            provider : 'kakao',
       });
       done(null, newUser);
  	}
   } catch (error) {
   		console.error(error);
        done(error);
    }
  }));
 };

🎉 코드 추가 해석
async (accessToken, refreshToken, profile, done)
-> accessToken, refreshToken은 로그인 성공 후 카카오가 보내준 토큰이다. 현재는 사용하지 않는다
-> profile은 카카오가 보내준 유저 정보이다.
-> profile의 정보를 바탕으로 카카오 회원가입도 가능한 것.

2. 카카오 로그인용 라우터 작성

// routes/auth.js

// GET /auth/kakao
router.get('/kakao', passport.authenticate('kakao'));

// GET /auth/kakao/callback
router.get('/kakao/callback, passport.authenticate('kakao', {
	failureRedirect: '/?loginError=카카오로그인 실패',
}), (req, res) => {
	res.redirect('/');
});

module.exports = router;

3. 카카오 로그인 앱 만들기

https://developers.kakao.com

-> 접속해서 회원가입 후 앱 키 저장.


🎉 위의 과정들이 모두 끝나면, passport 모듈을 이용해
회원가입/로그인/카카오로그인 모두 가능할 것이다.

profile
프론트엔드 개발자입니다 :)

0개의 댓글