Passport는 Express환경에서 Session을 활용한 로그인 및 Authentication, 즉 유저 인증을 도와주는 미들웨어 패키지이다.
현재 프로젝트에서 게시물 수정/삭제등의 권한은 Admin에만 있어야 하고, 현재는 Local DB의 인증 정보만을 활용하지만, 추후 댓글 기능 등을 추가하게 되면 어드민 뿐 아니라 일반 유저의 로그인을 받아야 하는데, 이 때 타 사이트 로그인 정보를 활용한 인증으로의 확장이 가능했기 때문에 Passport를 이용하게 되었다.
SessionDB.js
var session = require('express-session');
// session store를 위한 db 접근 express-mysql-session 객체 생성 후 전달
var MySQLStore = require('express-mysql-session')(session);
var options = {
host: '', // DB 위치한 호스트
port: 0000, // 및 포트
user: '', // 유저명
password: '', // 비밀번호
database: '' // 데이터베이스 명
};
var sessionStore = new MySQLStore(options);
module.exports = sessionStore;
passport.js
/*
for mysql passport
args : app : express() object
*/
module.exports = function(app){
const usermodel = require('../model/user');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function(user, done){ // 세션에 저장할 정보.
done(null, user);
})
passport.deserializeUser(function(id, done){ // 페이지마다 세션에서 보일 정보.
done(null, id);
})
passport.use(new LocalStrategy(
{
usernameField:'inputId', passwordField:'inputPassword'},
async function(username, password, done){
var result = await usermodel.authenticate(username, password);
if(result.length === 0){
return done(null, false, {message : 'Access Denied.'});
} else {
return done(null, result[0].id);
}
}));
return passport;
}
이 때 유의할 점은 다음과 같다.
serializeUser함수
passport.serializeUser(function(user, done){ // 세션에 저장할 정보.
done(null, user);
})
MariaDB의 sessions 테이블
boardController.js
const indexView = async(req, res) => {
...
adminLogin = false;
if(req.user === 'admin'){ // 세션에 접근하는 모습
adminLogin = true;
}
...
};
Authentication을 위한 LocalStrategy 세팅.
passport.use(new LocalStrategy(
{
usernameField:'inputId', passwordField:'inputPassword'},
// HTML단에서 inputId로 id 필드 정보를, passwordField를 통해 password 피드 정보를 넘긴다.
async function(username, password, done){
var result = await usermodel.authenticate(username, password);
if(result.length === 0){ // 실패시
return done(null, false, {message : 'Access Denied.'});
} else { // 성공시
return done(null, result[0].id);
}
}));
userModel.js
User.authenticate = function(userId, userPassword){
return new Promise((resolve, reject) => {
db.query("SELECT * FROM user WHERE id=? AND password=SHA2(?,256)",[userId, userPassword], function(err, result, fields){
if (err) throw err;
else {
resolve(result);
}
})
}).catch(error => console.log(error));
}
index.js (서버가 설치된 장소)
const sessionStore = require('./lib/sessionDB.js');
// 1에서 설정한 sessionDB
app.use(session({
key: 'nickname',
secret: 'mysecret',
store: sessionStore,
resave: false,
saveUninitialized: false,
cookie : {maxAge: 24 * 60 * 60 * 1000} // (1000 * 60 * 60 * 2) ms => 2hrs
}));
// Session 설치
const passport = require('./lib/passport')(app);
app.use('/admin', require('./routes/admin')(passport));
// 로그인을 받을 화면에 passport 객체 전달
/routes/admin.js (라우터)
const router = express.Router();
module.exports = function(passport){ // passport 자체를 객체로 받아야 하기 때문에 해당 함수 형태를 가짐.
router.get('/login', loginView);
router.post('/loginProcess', passport.authenticate('local',{
successRedirect : '/',
failureRedirect: '/admin/login',
failureFlash: true
})
);
router.get('/logoutProcess', logoutProcess);
return router;
};
loginView.ejs
<form class="form-signin" action="/admin/loginProcess" id="signinForm" method="post">
<h1 class="h3 mb-3 font-weight-normal">Admin sign in</h1>
<label for="inputID" class="sr-only">ID</label>
<input type="text" name="inputId" id="inputID" class="form-control" placeholder="ID" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" name="inputPassword" id="inputPassword" class="form-control" placeholder="Password" required>
<div class="checkbox mb-3">
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" id="signinBtn">로그인</button>
</form>
routes/admin.js
...
router.post('/loginProcess', passport.authenticate('local',{
successRedirect : '/', // 성공시 redirect
failureRedirect: '/admin/login', // 실패시 redirect
failureFlash: true
})
);
...
boardController.js
const indexView = async(req, res) => {
post = await indexmodel.aboutmeAll();
adminLogin = false;
if(req.user === 'admin'){ // 세션에 접근
adminLogin = true; // 유효하면 true
}
// 홈페이지 소개글 전부 select해서 index.ejs로 pass
res.render("index", {post, adminLogin}); // ejs 렌더링시 bool변수 넘김
// render
};
index.ejs
<ul class="nav nav-pills">
<li class="nav-item"><a href="/" class="nav-link active" aria-current="page">About Me</a></li>
<li class="nav-item"><a href="project" class="nav-link text-body">Projects</a></li>
<%if(adminLogin) {%> <!-- 접근 제어 부분. -->
<li class="nav-time"><a href="index_admin" class="nav-link text-body">Modify Index</a></li>
<%}%>
</ul>
login 화면
로그인 전 게시판 화면
로그인 후 게시판 화면. 글 편집이 가능한 모습을 볼 수 있다.