실제 로그인 / 회원가입 절차를 만들어보자
목차
- Authentication 이란?
- Credential을 사용하고 저장하기
- 시큐어 라우팅 (지금 프로젝트는 URL로 로그인 상태여야지 볼 수 있는 페이지 접근 가능)
- 로그인 한 유저와 로그인 하지 않은 유저를 식별해서 서로 다른 views 와 백앤드 로직을 제공하기 위해 하는 절차
- 주로 세션을 이용해 상태를 저장한다.
- 유저가 로그인 상태를 유지하면 쿠키에 세션 아이디를 저장하고 매 요청마다 세션이 유효한지 확인한다.
-
controllers/auth.js 에
postLogin추가exports.postSignup = (req, res, next) => { const email = req.body.email; const password = req.body.password; const confirmPassword = req.body.confirmPassword; // Email 겹치는지 확인하기 Uuser.findOne({ email: email }) .then(userDoc => { if (userDoc) { return res.redirect('/signup'); } const user = new User({ email: email, password: password, cart: { items: [] } }); return user.save(); }) .then(result => { res.redirect('/login'); }) .catch(err => { console.log(err); }); };
-
app.js mongoose 연결 부분 수정하기
mongoose .connect(MONGODB_URI) .then(result => { app.listen(3000); }) .catch(err => { console.log(err); });
-
$ npm i --save bcryptjs설치하기 -
controllers/auth.js 에
bcryptjs가져오고 패스워드에 해시 적용하기const bcrypt = require('bcryptjs') //... exports.postSignup = (req, res, next) => { const email = req.body.email; const password = req.body.password; const confirmPassword = req.body.confirmPassword; // Email 겹치는지 확인하기 User.findOne({ email: email }) .then(userDoc => { if (userDoc) { return res.redirect('/signup'); } // 패스워드 암호화 , 12번의 해싱 return bcrypt .hash(password, 12) .then(hashedPassword => { const user = new User({ email: email, password: hashedPassword, cart: { items: [] } }); return user.save(); }) .then(result => { res.redirect('/login'); }); }) .catch(err => { console.log(err); }); };
-
controllers/auth.js 에
postLogin수정하기exports.postLogin = (req, res, next) => { const email = req.body.email; const password = req.body.password; User.findOne({ email: email }) .then(user => { if (!user) { return res.redirect('/login'); } //bcrypt.compare(평문,해시값) => promise 리턴 bcrypt .compare(password, user.password) .then(doMatch => { // 패스워드까자 맞으면 실행 if (doMatch) { req.session.isLoggedIn = true; req.session.user = user; return req.session.save(err => { console.log(err); return res.redirect('/'); }); } // 패스워드가 일치하지 않으면 res.redirect('/login'); }) .catch(err => { console.log(err); res.redirect('/login'); }); }) .catch(err => console.log(err)); };
-
middleware/is-auth.js 파일 만들고 로그인 상태 체크 코드 넣기
module.exports = (req, res, next) => { if (!req.session.isLoggedIn) { return res.redirect('/login'); } next(); };
-
로그인이 필요한 라우터에 is-auth.js 파일 불러와서 상태 체크 하기
const isAuth = require('../middleware/is-auth'); router.get('/add-product', isAuth, adminController.getAddProduct); router.get('/products', isAuth, adminController.getProducts); ...
Cross - Site Request Forgery
교차 사이트 요청 위조
- 사용자가 로그인 한다.
- 로그인 상태에서 CSRF 공격 코드가 들어간 게시물을 확인한다.
코드에는 세션 ID가 포함 쿠키가 있는데 공격자는 이 쿠키를 탈취한다. - 공격자가 사용자 권한으로 Server에 요청을 보낸다. (자기 계좌로 송금 등)
요청이 들어 올 때마다 토큰이 있는지 확인한다. 토큰이 있어야 정상적인 실행.
토큰은 해시값으로 추측할 수 없다.
우리는 view와 server에 토큰을 포함할 것이다.
-
$ npm i --save csurf설치하기 -
app.js 에 CSRF 토큰 설정하기
const csrf = require('csurf'); // 토큰 설정 const csrfProtection = csrf(); app.use(csrfProtection);
-
controllers/shop.js
getIndex에csrf토큰 넣기exports.getIndex = (req, res, next) => { Product.find() .then(products => { res.render('shop/index', { prods: products, pageTitle: 'Shop', path: '/', isAuthenticated: req.session.isLoggedIn, csrfToken: req.csrfToken() }); }) .catch(err => { console.log(err); }); };
-
views/includes/navigation.ejs에
Logout에CSRF토큰 넣기<form action="/logout" method="post"> <input type="hidden" name="_csrf" value="<%= csrfToken %>" /> <button type="submit">Logout</button> </form>
-
app.js에
csrf추가하기// 모든 라우터에 csrf 토큰 적용하기 // res.locals은 view로 전달되는 로컬 변수를 설정함 app.use((req, res, next) => { res.locals.isAuthenticated = req.session.isLoggedIn; res.locals.csrfToken = req.csrfToken(); next(); });
-
모든 view 파일에
csrf토큰input추가하기<form action="/cart-delete-item" method="POST"> <input type="hidden" value="<%= p.productId._id %>" name="productId"> <button class="btn danger" type="submit">Delete</button> <input type="hidden" name="_csrf" value="<%= csrfToken %>" /> </form> ...
- 로그인에 실패 했을 때 사용자가 어떤 값을 잘못 입력했는지 피드백 해준다
-
$ npm i --save connect-flash -
app.js에
connect-flash가져오기const flash = require('connect-flash'); app.use(flash());
-
controllers/auth.js에
postLogin에서 에러 메세지req에 넣기exports.postLogin = (req, res, next) => { const email = req.body.email; const password = req.body.password; User.findOne({ email: email }) .then(user => { if (!user) { // flash(에러 이름, 메세지) req.flash('error', 'Invalid Email or Password!'); return res.redirect('/login'); } //bcrypt.compare(평문,해시값) => promise 리턴 bcrypt .compare(password, user.password) .then(doMatch => { // 패스워드까자 맞으면 실행 if (doMatch) { // 세션에 로그인 상태 저장 req.session.isLoggedIn = true; req.session.user = user; return req.session.save(err => { console.log(err); return res.redirect('/'); }); } // 패스워드가 일치하지 않으면 res.redirect('/login'); }) .catch(err => { console.log(err); res.redirect('/login'); }); }) .catch(err => console.log(err)); };
-
controllers/auth.js에
getLogin에 에러 정보 뷰에 렌더링하기exports.getLogin = (req, res, next) => { let message = req.flash('error'); // 에러면 message[0] = ['Invalid Email or Password!'] // 없으면 빈 배열 if (message.length > 0) { message = message[0]; } else { message = null; } res.render('auth/login', { path: '/login', pageTitle: 'Login', errorMessage: message }); };
-
views/auth/login.ejs 에 메세지 표시하기
<% if (errorMessage) { %> <div class="user-message user-message--error"><%= errorMessage %></div> <% } %>


