passport는 nodejs에서 사용자 인증 (login) 을 만들기 위해 널리 사용되는 패키지
단독으로 사용할 수 없고 passport strategy package와 함께 사용해야함
passport package : 인증 시스템을 위한 base
passport strategy package : 구체적인 인증 방법을 구현
이렇게 구성되는 이유는 사이트에 필요한 패키지만 설치하기 위해서임

passport 패키지, passport-local 패키지 설치
// index.js
...
var session = require('express-session');
var passport = require('./config/passport'); //1
var app = express();
...
// Passport // 2
app.use(passport.initialize()); //passport 초기화
app.use(passport.session()); //passport와 session 연결
// Custom Middlewares // 3
app.use(function(req,res,next){
//req.isAutehnticated는 현재 로그인이 되어 있는지 아닌지를 true나 false로 반환함 (passport에서 제공하는 함수)
res.locals.isAuthenticated = req.isAuthenticated();
//req.user는 로그인이 되면 session으로 부터 유저 정보를 deserialize 해서 생성함
res.locals.currentUser = req.user;
// res.locals.isAuthenticated에 로그인 했는지 안했는지 여부를 저장
// res.locals.currentUser로 로그인된 유저의 정보를 불러옴
next();
});
...
index.js (Coogle의 app.js)의 코드다.
설치한 모듈을 바로 require 하지 않고 config 디렉토리의 passport.js를 담아옴
설치한 모듈은 passport.js에 들어있음
session은 express-session 패키지로부터 생성되므로 로그인을 구현하기 위해서는 session생성 코드 app.use(session({secret:'MySecret', resave:true, saveUninitialized:true}));가 반드시 필요함
app.use에 함수를 넣는 것을 미들웨어라고함
app.use에 넣은 함수는 request url과 상관없이 request가 들어올 때마다 실행됨
그런데 위에 있는 것부터 실행되기 때문에 순서가 중요함
위의 경우 반드시 route보다 먼저 와야함
// config/passport.js
//그냥 이렇게 해
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy; // 1
var User = require('../models/User');
// serialize & deserialize User // 2
//serializeUser는 로그인 할 때 유저의 정보를 세션에 어떻게 저장할 지 정한다. id만 저장하게 함
passport.serializeUser(function(user, done) {
done(null, user.id);
});
//deserializeUser는 session에서 정보를 가져와서 유저를 만들 때 어떻게 만들지 정함
// id를 이용해서 가져옴
passport.deserializeUser(function(id, done) {
User.findOne({_id:id}, function(err, user) {
done(err, user);
});
});
// local strategy // 3
passport.use('local-login',
new LocalStrategy({
//이 부분은 로그인 폼에서 입력받을 때 name 속성값을 가져와서 usernameField와 passwordField로 사용할 수 있게 해준다
예를 들어 ejs 파일의 form에서 name="email"로 사용자가 id를 입력하면 usernameField : "email"로 해주면 된다.
usernameField : 'username', // 3-1
passwordField : 'password', // 3-1
passReqToCallback : true
},
//로그인 시에 호출되는 함수
function(req, username, password, done) { // 3-2
//db에서 유저 찾고 입력받은 password와 일치하면 done에 담아서 리턴
User.findOne({username:username})
.select({password:1})
.exec(function(err, user) {
if (err) return done(err);
//authenticate는 입력받은 password와 db에서 읽어온 해당 user의 password hash를 비교하는 함수
if (user && user.authenticate(password)){ // 3-3
return done(null, user);
//done의 첫번째 파라미터는 에러를 담기 위한 것
//에러가 없으면 null을 담는다
}
else { //일치하지 않을경우 flash 에러
req.flash('username', username);
req.flash('errors', {login:'The username or password is incorrect.'});
return done(null, false);
}
});
}
)
);
module.exports = passport;
// routes/home.js
var express = require('express');
var router = express.Router();
//상대주소라 점 2개 ../ 로 시작
var passport = require('../config/passport'); // 1
// Home ...
// Login // 2
//로그인 뷰를 보여줌
router.get('/login', function (req,res) {
var username = req.flash('username')[0];
var errors = req.flash('errors')[0] || {};
res.render('home/login', {
username:username,
errors:errors
});
});
// Post Login // 3
// 로그인 폼에서 입력 후 넘어오는 요청 처리, 콜백함수2개
router.post('/login',
function(req,res,next){
var errors = {};
var isValid = true;
if(!req.body.username){
isValid = false;
errors.username = 'Username is required!';
}
if(!req.body.password){
isValid = false;
errors.password = 'Password is required!';
}
if(isValid){
next();
}
else {
//유효하지 않은 입력일 경우 flash로 에러 발생시키고 로그인폼으로 리다이렉트
req.flash('errors',errors);
res.redirect('/login');
}
},
//passport의 local strategy를 호출해서 로그인 진행
passport.authenticate('local-login', {
successRedirect : '/posts',
failureRedirect : '/login'
}
));
// Logout // 4
//로그아웃 하고 리다이렉트
router.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});
module.exports = router;
npm install express-flash
import express from "express";
import flash from "express-flash";
const app = express();
/ 세션 코드 .... /
app.use(flash());
flash를 호출하면 템플릿에 1회성 메세지를 전달가능
req.flash("authorized", "Your Permission Denied.");
이렇게보내면 템플릿에서 authorized로 접근해서 Your permission denied를 쓸 수 있다.
일회성 메세지라 한번 보여주고 나면 세선에서 사라진다.
<!-- views/home/login.ejs -->
<!DOCTYPE html>
<html>
<head>
<%- include('../partials/head') %>
</head>
<body>
<%- include('../partials/nav') %>
<div class="container">
<h3 class="mb-3">Login</h3>
<form class="user-form" action="/login" method="post">
<div class="form-group row">
<label for="username" class="col-sm-3 col-form-label">Username</label>
<div class="col-sm-9">
<input type="text" id="username" name="username" value="<%= username %>" class="form-control <%= (errors.username)?'is-invalid':'' %>">
<% if(errors.username){ %>
<span class="invalid-feedback"><%= errors.username %></span>
<% } %>
</div>
</div>
<div class="form-group row">
<label for="password" class="col-sm-3 col-form-label">Password</label>
<div class="col-sm-9">
<input type="password" id="password" name="password" value="" class="form-control <%= (errors.password)?'is-invalid':'' %>">
<% if(errors.password){ %>
<span class="invalid-feedback"><%= errors.password %></span>
<% } %>
</div>
</div>
<% if(errors.login){ %>
<div class="invalid-feedback d-block"><%= errors.login %></div>
<% } %>
<div class="mt-3">
<input class="btn btn-primary" type="submit" value="Submit">
</div>
</form>
</div>
</body>
</html>
로그인을 위한 뷰

로그인 여부에 따라 접근제한
// util.js
var util = {};
util.parseError = function(errors){
...
}
//사용자가 로그인되어있는지 확인해서 로그인 안했을 경우 에러메세지와 함께 로그인페이지로 보낸다
util.isLoggedin = function(req, res, next){
if(req.isAuthenticated()){
next();
}
else {
req.flash('errors', {login:'Please login first'});
res.redirect('/login');
}
}
//접근권한이 없다고 판단된 경우에 로그인페이지로 보낸다
//req.isAuthenticated로 접근권한을 확인하지 않는 경우(다른 방법으로 판단하는경우)
util.noPermission = function(req, res){
req.flash('errors', {login:"You don't have permission"});
req.logout();
res.redirect('/login');
}
module.exports = util;