트립로그 마이그레이션 과정에서 로그인을 처리하기 위해 적용한 passport 사용방법에 대한 포스트입니다.
passport를 사용하여 로컬 로그인을 구현한 후, passport-kakao를 적용하려고 했으나, 카카오 개발자 페이지에서는 카카오 계정(email)의 수집에 대한 검수가 필요하다. 그래서 사용자의 이메일을 수집할 수 없었으며, 닉네임은 필수로 수집할 수 있지만, 내가 개발한 웹사이트에서는 중복된 닉네임 사용을 제한하고 있었기 때문에 카카오 로그인을 사용하는 사용자의 닉네임을 추가로 수집해야 하는 문제가 발생했다. 따라서, 위와 같은 이슈로 인해 passport-kakao를 사용하지 않고, 대신 passport를 사용하여 카카오 로그인을 구현했다.
Passport.js는 Node.js 기반 웹 애플리케이션에서 사용되는 인증 미들웨어이다. Passport.js를 사용하면 사용자 인증과 관련된 작업을 간단하게 처리할 수 있다.
Passport.js는 다양한 인증 전략(예: 로컬 인증, 소셜 미디어 인증 등)을 제공하여 사용자를 인증하는 방법을 표준화하고, 보안적인 인증 메커니즘을 구현하는 데 도움을 준다. 이를 통해 사용자 인증 로직을 쉽게 구현할 수 있으며, 다른 인증 서비스 (예: Google, Kakao, Twitter 등)와의 통합도 간단히 처리가 가능하다.
passport.js와 관련한 패키지를 설치
npm install passport passport-local express-session
passport 설정하기
const express = require('express');
const passport = require('passport');
const session = require('express-session');
passport 초기화 및 미들웨어 설정하기
const app = express();
// 세션 설정
app.use(session({
secret: 'mysecret', // 세션에 대한 암호화에 사용할 비밀키
resave: false, // 세션이 수정되지 않은 경우에도 세션을 다시 저장할지를 결정하는 옵션
saveUninitialized: false // 초기화되지 않은 새로운 세션을 저장할지를 결정하는 옵션
}));
// Passport 초기화
app.use(passport.initialize());
app.use(passport.session());
로컬 전략 설정하기
const LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
(username, password, done) => {
// 여기에 실제로 사용자 인증을 처리하는 로직을 작성
// 사용자가 제공한 username과 password를 검증하고, 인증 결과를 done 콜백에 전달
}
));
사용자 정보 직렬화 및 역직렬화
passport.serializeUser((user, done) => {
// 사용자 정보 중 세션에 저장할 필요가 있는 정보를 선택하여 done 콜백에 전달
});
passport.deserializeUser((id, done) => {
// 세션에 저장된 사용자 ID를 이용하여 사용자 정보를 조회하고 done 콜백에 전달
});
passport.serializeUser((user, done) => {
done(null, user.nickname);
});
serializeUser
에서 일반적으로 세션에 저장할 사용자 정보는 최소한으로 유지하는 것이 좋다. 사용자의 모든 정보를 세션에 저장하는 것은 리소스를 많이 사용할 수 있어 사용자의 닉네임(user.nickname
)만 세션에 저장한다.
passport.deserializeUser(async (nickname, done) => {
try {
const client = await mongoClient.connect();
conㅊst userdb = client.db('TripLogV2').collection('user');
const user = await userdb.findOne({ nickname });
done(null, user); // req.user
} catch (error) {
done(error, null);
}
});
deserializeUser
에서 세션에 저장된 닉네임(nickname
)을 매개변수로 받아 해당 사용자를 MongoDB에서 찾아 반환한다. 반환된 사용자 객체는 req.user
에 저장되어 사용이 가능해진다.
폴더구조
/passport/index.js
const passport = require('passport');
const local = require('./local');
const mongoClient = require('../routes/mongo');
module.exports = () => {
passport.serializeUser((user, done) => {
done(null, user.nickname);
});
passport.deserializeUser(async (nickname, done) => {
try {
const client = await mongoClient.connect();
const userdb = client.db('TripLogV2').collection('user');
const user = await userdb.findOne({ nickname });
done(null, user); // req.user
} catch (error) {
done(error, null);
}
});
local();
};
폴더구조
/passport/local.js
const passport = require('passport');
const { Strategy: LocalStrategy } = require('passport-local');
const mongoClient = require('../routes/mongo');
const crypto = require('crypto');
const verifyPassword = (password, salt, userPassword) => {
const hashed = crypto
.pbkdf2Sync(password, salt, 10, 64, 'sha512')
.toString('base64');
if (hashed === userPassword) return true;
return false;
};
module.exports = () => {
passport.use(
new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
},
async (email, password, done) => {
try {
// MongoDB에 연결
const client = await mongoClient.connect();
const userdb = client.db('TripLogV2').collection('user');
// 이메일로 사용자 검색
const user = await userdb.findOne({ email });
// 사용자가 존재하지 않는 경우
if (!user) {
return done(null, false, {
type: 'login',
success: false,
message: '해당 아이디를 찾을 수 없습니다.',
});
}
// 비밀번호 검증
const passwordCheckResult = verifyPassword(
password,
user.salt,
user.password
);
// 비밀번호가 일치하지 않는 경우
if (!passwordCheckResult) {
return done(null, false, {
type: 'login',
success: false,
message: '비밀 번호가 틀립니다.',
});
}
// 인증에 성공한 경우 사용자 정보 반환
return done(null, user);
} catch (error) {
console.error(error);
return done(error);
}
}
)
);
};
// 로컬 로그인(POST)
router.post('/local', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) {
console.error(err);
return next(err);
}
if (info) {
return res.send(info);
}
return req.login(user, async (error) => {
if (error) {
console.error(error);
return next(error);
}
return res.send({
type: 'login',
success: true,
message: '로그인 되었습니다.',
nickname: user.nickname,
});
});
})(req, res, next);
});
passport.authenticate
함수를 호출하여 사용자 인증을 수행한다.passport.authenticate('local')
함수는 로컬 전략을 사용하여 사용자를 인증하는 역할을 한다. 인증 과정에서는 입력된 이메일과 비밀번호를 확인하여 사용자를 검증하며 인증이 성공하면 콜백 함수가 호출된다.위의 콜백 함수에서는 다음과 같은 처리를 수행한다.