※ 목차
- 패키지 구조
- passport 동작 과정
- auth.js
- local strategy.js, index.js
- middlewares.js
- app.js 적용
- postman test
- 후기
0. 패키지 구조

1. passport 동작 과정
routes/auth.js
/login 으로 POST요청이 올경우 passport.authenticate실행.
첫번째 매개변수(전략)을 통해 내가 설정한 전략을 실행함.
* local 전략이라는 가정하에 작성
passport/localStrategy.js
LocalStrategy 객체에 내가 options으로 설정한 필드값을 검증받아 객체 생성
상수(이하 user)에 id를 req.params값으로 받아 db에서 id탐색
탐색 후 찾는 id가 있다면 비밀번호 검증 실행
검증 후 비밀번호가 일치하면 done(null, user) 리턴
id 혹은 비밀번호가 일치하지 않으면 message 전송
routes/auth.js
다시 돌아와 user값이 있으면 session에 값 저장
session id를 cookie에 저장
user가 없거나 서버에 에러가 났을경우 에러메세지 출력
passport/index.js
login 검증이 끝나면 passport.serializeUser실행
req.user 호출시 deserializeUser을 실행하여 response
위 설명을 그림으로 표현하자면 아래와 같다.

2. auth.js
2-1. 회원가입 로직 생성
root/models/member.js
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const Account = new mongoose.Schema({
//unique를 사용하여 중복방지, required를 사용하여 not null
username : {type:String, unique:true, required: true},
pw : {type:String, required: true},
nickName : {type:String, required: true},
// default:Date.now()를 사용하여 가입날짜 지정
regDate: {type:Date, default:Date.now()},
updateDate:{type:Date, default: Date.now()}
});
Account.statics.create = async function(payload) {
const member = new this(payload);
// bcrypt를 사용하여 pw 암호화
member.pw = await bcrypt.hash(payload.pw, 10);
return member.save();
};
module.exports = mongoose.model("Member", Account);
root/routes/members.js
const router = require('express').Router();
const Member = require('../models/member');
const register = router.post('/signup', (req, res) => {
Member.create(req.body)
.then(member => res.send(member))
.catch(err => res.status(500).send(err));
});
module.exports = router;
2-2. 로그인 로직생성
root/routes/auth.js
const express = require('express');
const passport = require('passport');
const bcrypt = require('bcrypt');
const Member = require('../models/member');
const {isLoggedIn, isNotLoggedIn} = require('./middlewares');
const router = express.Router();
router.post('/login', isNotLoggedIn, (req, res, next) => {
console.log('login user ->', req.body);
// local은 local전략을 사용하겠다는 뜻
// passport.authenticate가 실행되면 전략에 맞춰 구성해놓은 전략이 실행됨
// 예를들어 local로 지정해놨다하면 passport/localStrategy.js가 실행되어 회원검증을 함
passport.authenticate('local', (err, member, info) => {
if(err) {
console.error(err);
return next(err);
}
if(!member) {
return res.redirect(`/auth/?loginError=${info.message}`);
}
return req.login(member, loginError => {
if(loginError) {
console.error(loginError);
return next(loginError);
}
// return res.json({message:"로그인"});
// return res.send(member);
// 로그인 검증이 끝나면 http://localhost로 redirect
return res.redirect('/');
});
})(req, res, next);
});
router.get('/logout', isLoggedIn, (req, res) => {
req.logout()
req.session.destroy();
res.redirect('/')
});
module.exports = router;
* 필자는 Missing Credentials에러가 나서 에러를 해결했는데, 원인을 확인해보니 localStratege.js 설정중 회원을 제대로찾지 못해 나온 에러였다. 본인이 js를 잘 하지 못하면 log를 찍어 코드가 어디까지 통과했는지 어디서 막혔는지 잘 찾아내는게 중요하다.
3. LocalStrategy.js, Index.js
3-1. root/passport/localStrategy.js
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');
const Member = require('../models/member');
module.exports = () => {
passport.use(
new LocalStrategy(
{
// 본인이 구성한 Member Schema대로 필드값을 받아야한다
// 예를들어 id의 Documnet가 userId일경우 usernameField에 userId라고 적어야 한다.
// pw도 위와 같다.
usernameField : 'username', //
passwordField : 'pw',
},
// exUser를 먼저 찾아와야하기 때문에 콜백함수(async, await) 사용
async (username, pw, done) => {
try {
console.log(username);
// usernameField에 적혀진대로 username을 받아 db에 값이 있는지 찾는다.
const exUser = await Member.findOne({ username : username});
console.log("exUser : ", exUser)
if(exUser) {
// 값이 있는지 확인 후 pw 검증
const result = await bcrypt.compare(pw, exUser.pw)
if(result) {
// pw 일치시 done 호출
return done(null, exUser);
}else {
// pw 일치하지 않을경우 message 출력
done(null, false, {message : '비밀번호가 일치 하지 않습니다.'})
}
// exUser를 찾지못했을경우 message 출력
done(null, false, {message : '가입되지 않은 회원입니다.'})
}
}catch (err) {
console.error("err =",err)
done(err)
}
},
),
);
};
3-2. root/passport/index.js
const passport = require('passport');
const local = require('./localStrategy');
const Member = require('../models/member');
module.exports = () => {
// passport를 사용하여 로그인이 성공했을때 실행
passport.serializeUser((user, done) => {
console.log('serializeUser -> ', user)
done(null, user.username);
})
// 로그인한 유저정보를 호출했을시(req.user) 실행
passport.deserializeUser((username, done) => {
Member.findOne({username: username})
.then(user => done(null, user))
.catch(err => done(err));
})
local();
}
4. middlewares.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}`);
}
};
5. app.js 적용
require('dotenv').config();
const mongoose = require('mongoose');
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const indexRouter = require('./routes/index');
const bodyParser = require("body-parser");
const app = express();
const cors = require('cors')
/* cors start */
const corsOptions = {
origin: 'http://localhhost:3000', // 배포시 주석
// origin: 'https://pf6.chanyongyang.com:3000', // 배포시 주석 해제
credentials: true
}
app.use(cors(corsOptions));
/* cors end */
/* passport start */
const passportConfig = require('./passport');
const session = require('express-session');
const passport = require('passport');
passportConfig();
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(
session({
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
cookie: {
httpOnly:true,
secure: false // https일경우 true
},
}));
app.use(passport.initialize());
app.use(passport.session());
app.use('/auth', require('./routes/auth'));
/* passport end */
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
const Member = require('./models/member');
/* MongoDB connect start */
const uri = process.env.MONGODB_URI;
mongoose.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true });
const db = mongoose.connection;
db.on('error', console.error);
db.once('open', function(){
console.log("Connection Success");
});
/* MongoDB connect end */
module.exports = app;
6. postman test
6-1. 회원가입

6-2 login
6-2-1. root/routes/index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
if(req.user) {
console.log("req.user = ", req.user);
console.log(req.user);
console.log(req.user.username);
}
res.render('index', { title: 'Express' });
});
module.exports = router;
6-2-2. postman

6-2-3. log 출력
{
_id: new ObjectId("6502b77354db2b91d0174d3b"),
username: 'tistory',
pw: '$2b$10$/teaT1QROYwgjsOwQpR/F.55tVpoYQd1H3RjgOkJ/0bmYPNIppvim',
nickName: 'loginTest',
regDate: 2023-09-14T07:34:07.679Z,
updateDate: 2023-09-14T07:34:07.679Z,
__v: 0
}
tistory
7. 후기
js를 가지고 백엔드 구축하는데 있어 어려움을 많이 느꼈다.
다 하고나서 spring security의 인증부분과 상당히 비슷하다고 느꼈다(사실 처음부터 느꼈더라면 금방 구현했을텐데..)
이 글을 읽고있는 여러분도 에러없이 금방 구현하길 바란다.
'기타 라이브러리 및 프레임워크' 카테고리의 다른 글
jwt 관련 정리글 (만료시간, 토큰 생성 및 통신, 시나리오) (0) | 2023.10.25 |
---|