파이널 프로젝트를 시작한지 벌써 2주가 지났다.
첫번째주는 기획을 하고 이번주에는 로그인 관련 기능을 만들었는데, 로그인 기능을 구현하면서 사용한 기술에 대해 남겨보고자 한다.
이번 프로젝트에서는 소셜 로그인 기능(OAuth)를 지원하기 위해 Passport라는 라이브러리를 사용했습니다. passport를 이용하면 사용자에게 소셜 로그인 페이지를 지원하고, 손쉽게 사용자의 정보를 가져올수 있습니다.
router.get('/google', passport.authenticate('google', { scope: ['profile', 'email'] }),
router.get('/google/callback', passport.authenticate('google', { failureRedirect: '/failed' }),
async (req, res) => {
console.dir(req.user);
}));
위의 코드를 보면 google이라는 경로로 접속을 시도하면 사용자에게 로그인 정보를 요청하고, 사용자가 로그인에 사용할 계정을 선택하면 구글과 통신하여 인증과정을 거친뒤 google과 passport에서 설정한 callback url로 이동한뒤 다음과 같이 사용자의 정보를 사용할 수 있다.
// passport.js
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((obj, done) => {
done(null, obj);
});
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: 'http://localhost:5000/oauth/google/callback',
}, (async (accessToken, refreshToken, profile, done) => {
console.dir(profile);
done(null, profile);
})));
위의 코드는 passport에서 인증 과정을 처리하는 코드이다. 위의 코드를 간략하게 설명하면, authorization grant를 한 후 authorization code를 세션에 담고, 다시 요청을 보내 access token과 사용자 정보등을 받아와 사용자의 정보를 callback url로 넘겨준다.
사용자의 로그인 정보를 기록하기 위해 JWT를 사용하였다.
jwt: {
sign(payload) {
return jwt.sign({
exp: Math.floor(Date.now() / 1000 + (30 * 60)),
data: payload,
}, process.env.JWT_SECRET);
},
verify(token, callback) {
return jwt.verify(token, process.env.JWT_SECRET, callback);
}
userUtil이라는 파일을 만들어 jwt 관련 코드를 분리하였다. jwt의 payload부분에는 사용자의 이메일(ID)을 넣어 각 컴포넌트에서 현재 사용자의 정보가 필요할때 언제든 쉽게 요청할수 있다.
const email = userUtil.jwt.verify(req.session.userToken, (err, decoded) => {
if (err) return false;
return decoded.data;
});
기존에는 orm의 사용을 controller에서 사용하여 controller 코드의 볼륨이 커지고 가독성이 떨어졌다. 그래서 repository라는 디렉토리를 만들어 각 controller에서 사용하는 orm 코드들을 모아 관리하기로 했다.
export default {
getUserDataByEmail: async (email) => {
const response = await getConnection()
.getRepository(User)
.createQueryBuilder('user')
.where('user.email = :email', { email })
.getOne();
return response;
},
getUserDataByUsername: async (username) => {
const response = await getConnection()
.getRepository(User)
.createQueryBuilder('user')
.where('user.username = :username', { username })
.getOne();
return response;
}
.
.
.
위와 같이 orm 코드만 모아 각 contorller에서 가져와 사용하니 훨씬 깔끔하고 가독성이 좋아졌다.
// signup.js
async (req, res) => {
const { email, password } = req.body;
const response = await userRepository.getUserDataByEmail(email);
if (response) {
res.status(409).send('User conflict');
} else {
userRepository.insertSignUpUser(email, password);
res.status(200).send('Sign up success');
}