passport 사용해서 쉽게 로그인 기능을 구현해볼려고 한다. 배우는데는 어렵지만 배우고나면 유용하기 때문에 배운내용 기록해본다. 로그인 인증은 여러가지가 있지만 제일먼저 email, passport 로 로그인하는 passport-local 전략으로 구현해보겠다.
npm install passport
npm install passport-local
session
을통해서 정보를 소통하기 때문에 session middleware 를 사용해야한다. 추가: 기본세팅 과 구조.기본 app.js
const express = require('express');
const cors = require('cors');
const app = express();
const port = 3065;
const postRouter = require('./routes/post');
const userRouter = require('./routes/user');
const db = require('./models'); // models/index.js 에서 db 가져오기.
const passportConfig = require('./passport');
const session = require('express-session');
const passport = require('passport');
const cookieParser = require('cookie-parser');
const dotenv = require('dotenv');
dotenv.config();
db.sequelize
.sync() // 데이터베이스 시퀄라이즈 연결용
.then(() => {
console.log('db 연결 성공');
})
.catch(console.error);
passportConfig();
app.use(
cors({
// cors 문제 해결 npm i cors
origin: '*', // *: 모든도메인 허용
credentials: false, //
})
);
app.use(express.json()); //프론트에서 받은 데이터가 json 형태이먄 json 데이터를 req.body 에 넣어준다.
app.use(express.urlencoded({ extended: true })); // 프론트에서 받은 데이터가 form형식 데이터 일때 폼데이터를 req.body 에 넣어준다.
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(
session({
saveUninitialized: false,
resave: false,
secret: process.env.COOKIE_SECRET,
})
);
app.use(passport.initialize()); // 패스포트 설정 미들웨어에 추가.
app.use(passport.session());
app.use('/post', postRouter);
app.use('/user', userRouter);
app.listen(port, () => {
console.log(`app listening at http://localhost:${port}`);
});
패스포트 필수 설정
const express = require('express');
const app = express();
const passport = require('passport');
const passportConfig = require('./passport'); // passport 의 기본 설정을 따로 쪼개서 넣어놧다
passportConfig(); // passportConfig 불러오기.
app.use(
session({
saveUninitialized: false,
resave: false,
secret: process.env.COOKIE_SECRET,
})
); // 세션을 미들웨어에 추가 해줘야 passport 와 소통할수있다.
app.use(passport.initialize()); // 패스포트 설정 미들웨어에 추가.
app.use(passport.session());
userRouter.js:
const express = require('express');
const bcrypt = require('bcrypt');
const passport = require('passport');
const { User } = require('../models'); //모델 models/index 에서 가져오기
const router = express.Router();
router.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) {
console.log(err);
return next(err);
}
if (info) {
return res.status(401).send(info.reason); // 로그인 문제 생겻을때 보통 401로 접속.
}
return req.login(user, async (loginErr) => {
if (loginErr) {
console.log(loginErr);
return next(loginErr);
}
return res.status(200).json(user);
});
})(req, res, next);
});
passportConfig.js:
const passport = require('passport');
const local = require('./local'); // 로컬 에다가 passport-local 전략 구현
const { User } = require('../models');
module.exports = () => {
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findOne({
where: {
id,
},
});
done(null, user);
} catch (err) {
console.error(err);
done(err);
}
});
local(); // 전략 호출.
};
local.js: passport-local 전략
const passport = require('passport');
const { User } = require('../models');
const bcrypt = require('bcrypt');
const { Strategy: LocalStrategy } = require('passport-local');
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: '존재하지 않는 사용자입니다.' }); // done(서버에러,성공유무,클라이언트에러)
}
const result = await bcrypt.compare(password, user.password); // 해쉬암호화된 비밀번호가 일치한지 비교한다.
if (result) {
return done(null, user);
}
return done(null, false, { reason: '비밀번호가 틀렸습니다' });
} catch (error) {
console.log(error);
return done(error);
}
}
)
);
};
/login
주소로 req.body 로 전송outer.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => { //인증후 받는 콜백함수 done(err,user,info)
....
});
passport.use(
new LocalStrategy(
{
usernameField: 'email', // 프론트에서 받은 email (req.body.email)
passwordField: 'password', // 프론트에서 받은 password (req.body.password)
},
async (email, password, done) => {
try {
const user = await User.findOne({
// 비동기를 통해서 데이터베이스 User테이블에서 해당 로그인 email 이 있는지 확인하고 찾아준다.
where: {
email,
},
});
if (!user) {
// email 유저가 없다면
return done(null, false, { reason: '존재하지 않는 사용자입니다.' }); // done(서버에러,성공유무,클라이언트에러)
}
const result = await bcrypt.compare(password, user.password); // 해쉬암호화된 비밀번호가 일치한지 비교한다.
if (result) {
// 비밀번호가 맞다면
return done(null, user);
}
return done(null, false, { reason: '비밀번호가 틀렸습니다' });
} catch (error) {
//서버에러
console.log(error);
return done(error);
}
}
)
);
router.post('/login', (req, res, next) => {
//req,res,next 사용하기 위해서 확장 미들웨어 하는 코딩방식 진행
passport.authenticate('local', (err, user, info) => {
//done 콜백함수 여기서 받는다
if (err) {
// 서버 에러 낫을때
console.log(err);
return next(err);
}
if (info) {
// 클라이언트쪽 서버 문제 생겻을떄
return res.status(401).send(info.reason); // 로그인 문제 생겻을때 보통 401로 접속.
}
return req.login(user, async (loginErr) => {
// 정상적이게 user 받아왔을때 여기 진행되면서 동시에 serializeUser 쪽도 실행해야한다.
if (loginErr) {
//로그인 도중 에러
console.log(loginErr);
return next(loginErr);
}
return res.status(200).json(user); // 성공하면 user 정보를 프론트로 전송
});
})(req, res, next);
});
passport.serializeUser((user, done) => {
done(null, user.id); //백엔드 session 에 userid 저장 프론트에는 암호화된 user정보 를 쿠키에 저장.
});
passport.deserializeUser(async (id, done) => {
// 로그인 한 상태에서 매번 유저정보 불러올때 실행이 된다.
try {
const user = await User.findOne({
// User 모델에서 지금 로그인한 id 값과 일치한 정보를 user 에 담는다
where: {
id,
},
});
done(null, user); // 찾은 유저 반환. req.user 이렇게 정보를 저장
} catch (err) {
console.error(err);
done(err);
}
});