[Node JS] 로그인 회원가입 로그아웃 구현 #3 / 로그인 / JWT

Onam Kwon·2022년 4월 2일
0

Node JS

목록 보기
6/25
post-thumbnail

Sign-In implementation

▼로그인을 위한 server.js 전체 코드▼

코드복사는 아래 코드로 해주세요! 나중에 설명할때 나오는 코드는 모듈이 포함되어있지 않습니다!
만약 에러가 난다면 해당 모듈을 npm명령어를 통해 설치해주시고 import해주세요

const express = require('express');
const app = express();
app.use(express.static(__dirname + ''));

// allows you to ejs view engine.
app.set('view engine', 'ejs');

// importing .env file
require('dotenv').config();

// importing jsonwebtoken module.
const jwt = require("jsonwebtoken");

// importing body-parser to create bodyParser object
const bodyParser = require('body-parser');
// allows you to use req.body var when you use http post method.
app.use(bodyParser.urlencoded({ extended: true }));

const cookieParser = require('cookie-parser');
app.use(cookieParser());

// importing db function that connects with MongoDB.
const { db } = require('./module/db');

// importing auth function 
const { auth } = require('./module/authMiddleware');

// importing user schema.
const User = require('./module/user');

// importing bcrypt moudle to encrypt user password.
const bcrypt = require('bcrypt');
// declaring saltRounds to decide cost factor of salt function.
const saltRounds = 10;

//  To use python script
var PythonShell = require('python-shell');

db();

app.get('/login', auth, function(req, res) {
    const user = req.decoded;
    if(user) {
        return res.render('loggedin', {user:user.docs});
    } else {
        return res.sendFile(__dirname + '/login.html');
    }
});

app.post('/login/:signInid/:signInpw', function(req, res, next) {
    console.log('req.body: ', req.body);
    let user = new User(req.body);
    User.findOne({id:(user.id)}, function(err, docs) {
        if(err) throw err;
        else if(docs == null) { // Entered ID does not exist.
            return console.log('Entered ID does not exist.');
        } else {  // when entered ID matches.
            bcrypt.compare(user.pw, docs.pw, function (err, answer) {
                if (err) throw err;
                if(answer) {
                    req.user = docs;
                    return next();
                } else {
                    return res.send('Your password does not match with your ID.');
                }
            })
        }
    });
});

app.post('/login/:signInid/:signInpw', function(req, res) {
    const docs = req.user;
    const payload = { // putting data into a payload
        docs,
    };
    // generating json web token and sending it
    jwt.sign(
    payload, // payload into jwt.sign method
    process.env.SECRET_KEY, // secret key value
    { expiresIn: "30m" }, // token expiration time
    (err, token) => {
        if (err) throw err;
        else {
            return res
            .cookie('user', token,{maxAge:30*60 * 1000}) // 1000 is a sec
            .end();
        }
    });
});

3. 회원가입 구현

처음 유저가 /login경로에 들어왔을때, 유저는 아래와 같은 화면을 마주쳐야한다.

그리고 유저가 로그인에 성공했다면 아래와 같은 화면을 마주쳐야한다.

유저 로그인이 유지가 되도록 JWT토큰을 사용하면 /경로에 가더라도 아래처럼 로그인 유지가 가능하다.

아래는 /경로에서 로그인 되어있지 않는 경우이다.

ejs

아래처럼 서버에서 /login경로로 응답을 보낼때 로그인된 경우와 되지 않은 경우를 나눠서 보내야 하므로 user값이 있는지와 없는지를 확인후 없다면 평소처럼 login.html파일을, 있다면 매번 달라지는 유저 정보와 함께 응답해야한다. 이때 ejs가 필요하다.
user값이 있는지 확인하기 위해 req.decoded를 불러오는 과정은 좀 더 아래에서 나온다.

// allows you to ejs view engine.
app.set('view engine', 'ejs');  
app.get('/login', auth, function(req, res) {
    const user = req.decoded;
    if(user) {
      // 왼쪽 user는 ejs템플릿 파일에서 사용되어질 변수 이름
      // 오른쪽 user.docs는 템플릿에 실제 넣을 데이터값.(이 값에 유저정보 있음).
        return res.render('loggedin', {user:user.docs});
    } else {
        return res.sendFile(__dirname + '/login.html');
    }
});

ejs는 아래 명령어를 통해 설치할 수 있다.

npm install ejs

ejs를 사용할땐 html파일이 아닌 ejs확장자를 가진 파일을 따로 사용해야한다.
그래서 login.html파일을 복사후 views폴더에 loggedin.ejs라는 이름으로 복사해줬다.
views폴더를 만들고 따로 관리하는 이유는 파일이 모일수록 번거롭기 때문이다. (별거없음)
ejs파일은 이런식으로 사용할 수 있다.

<!-- loggedin.ejs -->
<!-- 이런식으로 서버에서 보낸 매번 달라지는 데이터가 자동으로 파일에 들어간다. -->
<h1><%= user.id %>! Welcome<br>
	Your address: <%= user.address %>. <br>
	You are: logged in. <br>
	This is your private page! <br>
</h1>

JWT token

로그인을 구현할 때 json web token 이라는 것을 발급해준다.
이유는 http요청을 할때마다 매번 유저정보를 서버와 클라이언트 사이에서 교환하면 보안적으로 좋지 않고 user DB에서 매번 검색하면 생각보다 이 작업도 무겁기 때문이다.
그래서 토큰이라는것을 발급해주는데 처음 로그인에 성공했을 경우 토큰을 발급해준다.

▼JWT토큰을 사용한 로그인 절차▼
1. 클라이언트에서 처음에 로그인 정보를 입력해 로그인시도
2. 서버에서 bcrypt모듈을 이용해 로그인정보와 user DB에 있는 암호화된 비밀번호 비교
3. 로그인 성공후 서버에서 토큰 발급
4. 발급한 토큰을 쿠키에 저장해서 클라이언트에 응답.
5. 유저가 로그인이 계속 필요한 부분에서 http 요청
6. 서버에서 쿠키에 저장되있는 토큰을 확인해 user ID사용가능, user DB에 있는 정보와 일치하는지 권한 확인
7. 권한이 충분하다면, 5번의 요청에 대한 응답

아래는 클라이언트에서 로그인정보를 입력하고 서버에 데이터값을 보냈을때의 onclick함수이다.

</script>
	function signInAjax() {
		const signinID = document.getElementById("signinID").value;
		const signinPW = document.getElementById("signinPW").value;
		document.getElementById("signinID").value = "";
		document.getElementById("signinPW").value = "";
		$.ajax({
			type: "post",
			url: 'http://localhost:8080/login/:signInid/:signInpw',
			data: {id:signinID,pw:signinPW},
			dataType:'text',
			success: function(res) {
				location.reload();
			}
		});
	}
</script>

아래는 요청받은 정보를 이용해 유저를 검색하고 있다면 user DB에 암호화로 저장되어진 비밀번호와 입력받은 비밀번호를 bcrypt모듈을 이용해 비교하는 미들웨어다.

app.post('/login/:signInid/:signInpw', function(req, res, next) {
  	// initializing user variable using schema
    let user = new User(req.body);
    User.findOne({id:(user.id)}, function(err, docs) {
        if(err) throw err;
        else if(docs == null) { // Entered ID does not exist.
            return console.log('Entered ID does not exist.');
        } else {  // when entered ID matches.
		    // user.pw -> user entered password and docs.pw -> encrypted user DB password
            bcrypt.compare(user.pw, docs.pw, function (err, answer) {
                if (err) throw err;
                if(answer) {
                  // req.user variable can be used in next middleware
                    req.user = docs;
                    return next();
                } else {
                    return res.send('Your password does not match with your ID.');
                }
            })
        }
    });
});

비밀번호가 일치해서 유저 확인이 되었다면 next()메소드를 통해 다음 미들웨어로 보내준다.
아래 미들웨어에서는 JWT를 발급하고 쿠키로 저장한다.
process.env.SECRET_KEY
.env파일에 SECRET_KEY = 'jwtSecret';를 추가해줬다. 보안에 중요한 파일이므로 따로 보관해준다.

.env파일이 있는 경로에서 아래 터미널 명령어 사용 또는 SECRET_KEY = 'jwtSecret';복붙.

echo >> .env "SECRET_KEY = 'jwtSecret';"

토큰 발행후 user라는 이름으로 쿠키에 저장해 클라이언트에 응답해준다.
토큰이 유효기한과 쿠키의 유효기한은 30분으로 같게 설정해줬다. 추가로 쿠키에서의 숫자단위는 millisecond이므로 1/1000초다. 따라서 1000 = 1초.
나중에 쿠키를 없애는 방법으로 로그아웃도 구현할 수 있다.

// importing .env file
require('dotenv').config();

app.post('/login/:signInid/:signInpw', function(req, res) {
    const docs = req.user;
    const payload = { // putting data into a payload
        docs,
    };
    // generating json web token and sending it
    jwt.sign(
    payload, // payload into jwt.sign method
    process.env.SECRET_KEY, // secret key value
    { expiresIn: "30m" }, // token expiration time
    (err, token) => {
        if (err) throw err;
        else {
            return res
            .cookie('user', token,{maxAge:30*60 * 1000}) // 1000 is a sec
            .end();
        }
    });
});

아래에서 쿠키가 브라우저에 저장됨을 확인할 수 있다.

JWT auth

토큰을 발급받았으면 jwt모듈의 auth메소드를 이용해 토큰을 확인할 수 있다.
authMiddleware.js파일을 새로 만들어주고 아래와 같이 만들었다.
생각보다 별거 없이 jwt.verify()함수에 쿠키값과 비밀키만 넣으면 된다.
나머지는 에러처리.
authMiddleware.js파일 전체코드▼

const jwt = require('jsonwebtoken');
const path = require('path');
// importing .env file
require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); 

exports.auth = (req, res, next) => {
    // 인증 완료
    try {
        // 요청 헤더에 저장된 토큰(req.headers.authorization)과 비밀키를 사용하여 토큰을 req.decoded에 반환
        req.decoded = jwt.verify(req.cookies.user, process.env.SECRET_KEY);
        return next();
    }
    // 인증 실패
    catch (error) {
        // Token has been expired
        if (error.name === 'TokenExpiredError') {
            console.log('auth TokenExpiredError');
            next();
            // return res.status(419).json({
            //     code: 419,
            //     message: 'Token has been expired.'
            // }); 
        }
        // JsonWebTokenError
        if (error.name === 'JsonWebTokenError') {
            console.log('JsonWebTokenError');
            next();
            // return res.status(401).json({
            //     code: 401,
            //     message: 'Invalid token.'
            // });
        }
    }
}

auth함수로 가져와 준 다음 필요한 미들웨어에 그냥 아래처럼 넣으면 사용되어진다.
auth함수가 브라우저의 쿠키를 확인해 유저를 확인한 다음 user변수에다가 결과를 리턴한다.
user가 있다면 그정보를 바탕으로 ejs파일과 함께 클라이언트에 응답할것이고
user가 없다면 비어있는 값이므로 html파일만 클라이언트에 응답한다.

// importing auth file
const { auth } = require('./module/authMiddleware');

app.get('/login', auth, function(req, res) {
    const user = req.decoded;
    if(user) {
        return res.render('loggedin', {user:user.docs});
    } else {
        return res.sendFile(__dirname + '/login.html');
    }
});

위 코드처럼 처음 로그인에 성공해서 토큰만 가지고있다면 원하는 경로에 auth함수를 집어넣어 토큰 확인을 통해 유저 권한이 필요한 부분에 적용할수 있다. 아이디에 맞춰서 주소값이 변하는걸 확인할 수 있다.
▼최종 결과물▼

profile
권오남 / Onam Kwon

0개의 댓글