노드 라이브러리 패스포트는 유저네임/비밀번호를 이용한 인증 뿐만 아니라 페북/트위터 소셜 인증, jwt 인증 등 방법이 무척 많다. 공통의 인증 로직은 passport가 담당하고 구체적인 방법은 "전략"
이라는 개념으로 분리해 놓았다.
passport-local
passport-facebook, passport-twitter
passport-jwt
이번에 포스팅할 전략은 유저네임/비밀번호 전략이다.
유저네임/비밀번호를 이용한 "로컬 전략"이 사용한 인증 수단이다. passport-local 패키지를 사용해서 진행한다.
const passport = require('passport');
const { Post, User } = require('../../models');
router.post('/login', (req, res, next) => {
//미들웨어 확장
//여기서 passport전략으로 간다.
passport.authenticate('local', (err, user, info) => {
if (err) {
console.error(err);
return next(err);
}
if (info) {
return res.status(401).send(info.reason);
}
//여기서 serializeUser가 실행
return req.login(user, async (loginErr) => {
if (loginErr) {
console.error(loginErr);
return next(loginErr);
}
const fullUserWithoutPassword = await User.findOne({
where: { id: user.id },
attributes: {
exclude: ['password'],
},
include: [
{
model: Post,
},
{ model: User, as: 'Followings' },
{ model: User, as: 'Followers' },
],
});
console.log(fullUserWithoutPassword);
return res.status(200).json(fullUserWithoutPassword);
});
})(req, res, next);
});
클라이언트 서버로부터 post요청이 왔을 시 passport.authenticate
를 통해 passport전략을 수행한다. 처리되는 내용은 아래 파일과 같다.
onst passport = require('passport');
const { Strategy: LocalStrategy } = require('passport-local');
const { User } = require('../../models');
const bcrypt = require('bcrypt');
module.exports = () => {
passport.use(
new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
},
async (email, password, done) => {
try {
const user = await User.findOne({
//이메일 검사
where: { email },
});
if (!user) {
//이메일 없으면?
return done(null, false, { reason: '존재하지 않은 이메일입니다!!' }); //서버에러, 성공, 클라이언트에러
}
const result = await bcrypt.compare(password, user.password); //이메일이 존재한다면 비밀번호 비교
if (result) {
return done(null, user);
}
return done(null, false, { reason: '비밀번호가 틀렸습니다' });
} catch (error) {
console.error(error);
return done(error);
}
},
),
);
};
Strategy
은 옵션 객체와 인증 정보로 사용자를 찾는 콜백함수를 받는다. 다시 routes/user.js
를 확인해보면 return req.login(user, async (loginErr) =>
이 부분에서 serializeUser가 실행이 된다. 아래 passport/index.js
코드를 확인해보자.
const passport = require('passport');
const local = require('./local');
const { User } = require('../../models');
module.exports = () => {
passport.serializeUser((user, done) => {
done(null, user.id); //세션에 유저 id만저장
});
//로그인 성공 후 그 다음 요청부터 deserializeUser가 실행
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findOne({ where: { id } }); //메모리에 저장된 id를 찾아서 가져온다.
done(null, user); //req.user에 저장.
} catch (error) {
console.log(error);
done(null, error);
}
});
local();
};
여기서 serializeUser
와 deserializeUser
가 등장한다.
serizliseUser
이다.deserializeUser
다.[추가설명]
serialize는 직렬화
, deserialize는 역직렬화이다.
직렬화라는 것은 어떤 데이터를 다른 곳에서 사용할 수 있게 다른 포맷의 데이터로 바꾸는 것을 의미한다. 지금 패스포트에서는 시퀄라이즈 객체를 세션에 저장할 수 있는 데이터로 바꾸고 있다.
반대로 역직렬화는 다른 포맷의 데이터로 바뀐 데이터를 원래 포맷으로 복구하는 것이다. 세션에 저장된 데이터를 다시 시퀄라이즈 객체로 바꾸는 작업을 의미한다.
deserialize에서 복구된 값은 req.body가 아니라 req.user에 들어간다. 세션쿠키를 통해서 메모리에 저장된 id를 찾아서 가져오는 것이다.
일단 passport.session이 deserializeUser을 실행하지만. deserializeUser는 serializeUser 후에 모든 라우터 접근 시 실행된다.
[브라우저 네트워크 확인]
아래 사진과 같이 로그인을 진행했을 때 네트워크 탭에서 헤더에 세션id가 포함 된 쿠키
가 들어온다는 것을 확인할 수 있다.