JavaScript/Nodejs

[Nodejs] 로그인 / 회원가입 [JWT Token]

반응형

글이 잘 보이지 않는다면 우측 하단의 달 모양을 눌러 라이트모드로 변경해주시기 바랍니다.

해당 글은 MacOS 를 기준으로 작성이 되었음을 미리 알려드립니다. WindowOS  를 이용하시는 분은 추후 업로드될 게시글을 기다려주시거나 다른 작성자의 게시글을 확인해주시기 바랍니다.

 


  JWT를 이용하는 나만의 이유

예전에는 Session 을 이용해 자동로그인, 인증등을 하고 있었습니다. 하지만 이 기능은 구현하기가 복잡하기도 하고 DB 혹은 서버에 많은양의 데이터를 보관/처리를 하기때문에 성능상 이슈가 발생하고 있었습니다.

 

이러한 이슈를 해결하기 위해서어떤 방법이 있을까 하다가 찾게 된것이 JWT  였습니다.

JWT 는 AccessToken과 RefreshToken 을 클라이언트에서 가지고 있다가 필요할때만 서버에 요청하여 인증을 받는 방식이었는데 해당 방식이 서버의 성능을 크게 망치지 않는다 생각하여 채택을 하게 되었습니다.

 

  JWT Token 알아보기

Json Web Token 의 약자로,

HEADER , PLAYLOAD , VERIFY SIGNATURE  로 나뉩니다.

https://jwt.io

 

 >> JWT 동작원리

최초 로그인은 아래와같은 동작원리로 되어있습니다.

 

이 후 자동 로그인을 할때는 아래의 동작이 이루어지게 됩니다.

 

 

  JWT ( Json Web Token ) 설치 및 적용

yarn add jsonwebtoken
yarn add dotenv

위와같이 설치를 진행해줍니다.

 

이 후 원하는 곳에 jwt.js 를 만들어주고 코드를 작성해줍니다.

// jwt.js

require("dotenv").config();
const jwt = require('jsonwebtoken');

이렇게 작성을 해주셨다면, .env 파일을 만들어 아래와같이 작성을 해 줍니다.

// .env
ACCESS_TOKEN_SECRET="YOUR_ACCESS_KEY"
REFRESH_TOKEN_SECRET="YOUR_REFRESH_KEY"

 

이렇게까지 했으면 어느정도 준비 과정은 끝이났습니다.

 

이제 로그인 후 JWT 토큰 발급하는 과정을 해보겠습니다.

 

 

  JWT 토큰발급

jwt.js 안에서 아래와같이 작성을 해주도록 하겠습니다.

// jwt.js

const token = () =>{
    return{
        access(id){
            return jwt.sign({id}, process.env.ACCESS_TOKEN_SECRET , {
                expiresIn: "15m",
            });
        },
        refresh(id){
            return jwt.sign({id}, process.env.REFRESH_TOKEN_SECRET , {
                expiresIn: "180 days",
            });
        }
    }
}

이 함수를 이용하여 최초 로그인을 했을 때 accessToken 과 refreshToken  을 발급해주는 부분 입니다.

굳이 이렇게 따로 분리한 이유는, 추후 accessToken 은 refreshToken 에 의해 새로 발급을 해주기위해서입니다.

 

이제 인증을 하는 부분을 만들어보도록 하겠습니다.

// auth.js
router.get('/user' , (req,res)=>{
})

우리가 흔히 알고있는 라우팅 방법은 이렇게 되어있습니다.

하지만 이제는 조금 다른 형식으로 하려고 합니다.

router.route('/user')
    .get( (req, res)=>{} )

이런식으로 진행을 하고자 합니다.

여기서 ( req, res ) => {}  이 부분도 따로 떼어내 userLogin 으로 만들도록 하겠습니다.

exports.userLogin = (req,res) => {
    res.send('userLogin');
}

이런 형식으로 따로 만들어주도록 합니다.

 

그리고 jwt.js 로 돌아가서

//jwt.js
exports.authenticate = (req, res, next) => {

    if(req.query.id === 'hello'){ // id 가 일치할 때
        req.authData = {
            status : 200,
            message : 'Correct User Data',
            jwt:{
                accessToken : token().access(req.query.id),
                refreshToken : token().refresh(req.query.id)
            }
        };
    }else{ // 일치하지 않을 때
        req.authData = {
            status : 400,
            message : 'Not Correct User Data'
        };
    }

    next();
}

아래와 같은 코드를 작성해주도록 합니다.

그리고 auth.js 로 돌아가 미들웨어를 추가로 작성해주도록 합니다.

//auth.js
const router = require('express').Router();

const user = require('../../controller/user')
const {authenticate} = require('../../lib/auth/jwt')

router.route('/user')
    .get( authenticate , user.userLogin )

module.exports = router

 

이렇게 작성을 해 주시고 user.js 파일 안에서 마무리로 작업을 해 줍니다.

//user.js
exports.userLogin = (req,res) => {
    const userData = req.authData;

    if(userData.status === 200){
        res.json(userData.jwt)
    }else{
        res.send('ERROR')
    }
}

이렇게 하면 최초 로그인시 토큰 발급은 끝이나게 됩니다.

 

  JWT 자동로그인

이제 자동 로그인을 해볼 차례 입니다.

위 순서를 보면 자동로그인 시 AccessToken 을 체크하게 됩니다.

//auth.js
router.route('/token')
    .get(authenticateAccessToken, (req,res)=>{
        console.log(req.user)
        res.send(req.user)
    })

 

위 코드를 보면 get 방식으로 보내게 되는데 AccessToken 을 쿼리에 담아서 보내면 많이 어려운 부분이 있기때문에 헤더에 담아서 보내도록 하겠습니다.

//jwt.js
const authenticateAccessToken = (req, res, next) => {
    let authHeader = req.headers["authorization"];
    let token = authHeader && authHeader.split(" ")[1];

    if (!token) return res.sendStatus(400);

    jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (error, user) => {
        if (error) return res.sendStatus(403);

        req.user = user;
        next();
    });
}

 

/auth/token 으로 요청을 보내게 되면 우선적으로 authenticateAccessToken 함수의 미들웨어로 접근을 하게 됩니다. 이 미들웨어 안에서는 헤더 안에 authoriztion 이라는 이름으로 보내주면 토큰과 아이디로 나누어 토큰의 유무와 검증을 하게 됩니다. 이 후 정상적인 결과를 가져오게 된다면 req.ueser 안에 user 정보를 담아 다음 미들웨어로 넘어가게 되는데 위 auth.js 를 보시면 req.ueser 안의 결과를 받아올 수 있게 됩니다.

코드를 잘 정리해주셧다면 이처럼 Header에 담아서 보낼건데 authorization 이름으로 "id accessToken" 순으로 넣어주세요. 가운데에 띄어쓰기는 옵션입니다.

이 안에 특수문자를 넣어서 대처할 수 있습니다.

 

expiresIn 에서 적용한 시간이 다 되었을 시 아래와 같이 뜨게되며

AccessToken 을 담아서 보내지 않으면 이와같이 나타나게 됩니다.

expiresIn 에서 적용한 시간이 다 되지 않고 정상적인 AccessToken 을 받아오게 된다면 이와같은 값을 보내주게 됩니다.

 

 

  JWT 자동로그인 AccessToken 만료

위에서 만료가 되었을 시 403 Forbidden 오류를 뿜었습니다. 그럼 프론트에서는 이 오류를 받았을 때 refreshToken 과 함께 새로운 AccessToken 을 발급받을 수 있도록 해주면 되겠지요?

 

//auth.js
router.route('/token')
    .get(authenticateAccessToken, (req,res)=>{
        console.log(req.user)
        res.send(req.user)
    })
    .post((req, res) => {
        const refreshToken = req.body.refreshToken;
        if(!refreshToken) return res.sendStatus(403);
        const accessToken = token().issuance(refreshToken, res)
        console.log(accessToken)
        res.json({accessToken})
    })

 

기존 경로에 post 형식으로 보낼 수 있도록 해주겠습니다.

한 경로에 post , get , put , delete 등 메서드별 기능을 다룰 수 있는 CRUD  에 대해서는 나중에 소개하도록 하겠습니다.

 

자 그럼 코드를 풀어보자면, 우선 body에 refreshToken 을 담아 서버로 보내주고, refreshToken을 보내주지 않았다면 403 에러를 뿜어주게 해줍니다. 하지만, 토큰이 있다면 token().issuance() 함수에 refreshToken 과 res 를 보내줍니다.

// jwt.js

const token = () =>{
    return{
        access(id){
            return jwt.sign({id}, process.env.ACCESS_TOKEN_SECRET , {
                expiresIn: "15m",
            });
        },
        refresh(id){
            return jwt.sign({id}, process.env.REFRESH_TOKEN_SECRET , {
                expiresIn: "180 days",
            });
        },
        issuance(token, res) {
            return jwt.verify(
                token,
                process.env.REFRESH_TOKEN_SECRET,
                (err, user) => {
                    if (err) res.sendStatus(403)
                    const key = this.access(user.id)
                    return key
                }
            )
        }
    }
}

 

클로저를 이용하여 이런식으로 함수를 선언해주도록 합니다.

issuance 함수에서   jwt.verify  를 통하여 refreshToken과 기존의 REFRESH_TOKEN_SECRET 을 이용해 유효성 검사를 해주고, 문제가 있다면 403 Forbidden 오류를, 없다면 새로운 AccessToken 을 발급해서 보내주도록 합니다.

 

이 후 프론트에서 로그인 처리를 해주면 되겠습니다.

 

반응형