Koa에서 JWT(JSON Web Token)으로 토큰기반인증 구현하기

Seokwon Han·2020년 11월 13일
0

개발일지

목록 보기
1/7

인증수단

현재 진행중인 개인 프로젝트에서는 Node.js의 웹 프레임워크인 Koa를 사용하여 API 서버를 구축하고 있다.

기본적으로 로그인이 필요한 서비스를 만들고 있기 때문에 로그인을 한 유저의 인증 방식이 필요했다.
따라서 적절한 인증수단이 필요했는데 JWT(JSON Web Token)라는 토큰기반 인증시스템을 알아보았고 이를 사용하기로 했다.

왜 JWT인가?

자가수용적

JWT 자체적으로 필요한 모든 정보를 포함한다. 헤더 정보, 실제 전달할 데이터, 검증할 수 있는 서명 데이터를 모두 포함하고있다.

넓은 지원범위

많은 프로그래밍 언어에서 지원하는 시스템이며 구글에 검색해보면 관련 모듈을 쉽게 찾아서 사용해볼 수 있다.

서버 자원 절약

토큰 기반 인증시스템이기 때문에 서버는 전달받은 토큰을 검증하고 이에 맞는 응답을 해주면 된다. 따라서 클라이언트의 상태를 서버에 저장할 필요가 없어서 서버 자원이 절약된다.

모듈화

먼저 JWT를 사용하기 위해서 'jsonwebtoken' 모듈을 다운받았다.
그리고 token.js파일을 만들어서 토큰생성함수, 토큰디코더함수, JWT처리 미들웨어를 작성했다.
또한 생성된 토큰은 쿠키에 넣어서 사용하기로 했다.

lib/token.js

const jwtSecret = process.env.JWT_SECRET;
const jwt = require('jsonwebtoken');

//JWT 토큰 생성
function generateToken(payload) {
    return new Promise(
        (resolve, reject) => {
            jwt.sign(
                payload,
                jwtSecret,
                {
                    expiresIn: '7d'
                }, (error, token) => {
                    if(error) reject(error);
                    resolve(token);
                }
            );
        }
    );
};
exports.generateToken = generateToken;

// JWT 디코딩
function decodeToken(token) {
    return new Promise(
        (resolve, reject) => {
            jwt.verify(token, jwtSecret, (error, decoded) => {
                if(error) reject(error);
                resolve(decoded);
            });
        }
    );
}

// JWT 처리 미들웨어
exports.jwtMiddleware = async (ctx, next) => {
    const token = ctx.cookies.get('access_token'); // ctx 에서 access_token 을 읽어옵니다

    if(!token) return next(); // 토큰이 없으면 바로 다음 작업을 진행합니다.

    try {
        const decoded = await decodeToken(token); // 토큰을 디코딩 합니다

        // 토큰 만료일이 하루밖에 안남으면 토큰을 재발급합니다
        if(Date.now() / 1000 - decoded.iat > 60 * 60 * 24) {
            // 하루가 지나면 갱신해준다.
            const { _id, profile } = decoded;
            const freshToken = await generateToken({ _id, profile }, 'account');
            
            // 쿠키에 설정
            ctx.cookies.set('access_token', freshToken, {
                maxAge: 1000 * 60 * 60 * 24 * 7, // 7days
                httpOnly: true
            });
        }

        // ctx.request.user 에 디코딩된 값을 넣어줍니다
        ctx.request.user = decoded;
    } catch (e) {
        // token validate 실패
        ctx.request.user = null;
    }

    return next();
};

함수 설명

generateToken 함수는 새로운 토큰을 생성하는 함수다. payload에는 사용자의 profile 정보가 들어있으며 payload, 비밀키, 유효기간을 설정하여 토큰을 생성할 수 있다.

decodeToken 함수는 비밀키값을 통해 토큰을 디코딩하여 토큰의 유효성을 검사한 뒤 토큰에 담긴 데이터를 반환한다.

jwtMiddleware 는 쿠키에 토큰이 있는지 확인하고, 토큰이 있다면 토큰을 디코딩하고 필요시 갱신도 해준다.
그 후에 request 객체에 클라이언트가 보낸 데이터를 담아서 토큰 인증 여부를 라우터에서 알 수 있게 한다.

미들웨어 사용

모듈화한 미들웨어는 index.js에서 불러와 사용하였다.

index.js

const Koa = require('koa');
const cors = require('@koa/cors');

const app = new Koa();
const { jwtMiddleware } = require('lib/token'); // jwt 미들웨어

// JWT 처리 미들웨어 적용
app.use(jwtMiddleware); 

...

const port = process.env.PORT || 4000;
app.listen(port, () => {
    console.log('bgs server is listening to port ' + port);
});

이제 클라이언트로부터 요청이 들어온다면 jwt 처리 미들웨어를 거치게 되고, 인증 여부에 따라 request 객체에 유저의 profile 정보가 담기게된다.

CORS 이슈

로컬에서 프로젝트를 진행하다가 클라이언트와 서버를 각각 다른 도메인에 호스팅하고 진행을 하게되었다. 그러고나서 쿠키가 잘 전달되는지를 확인해보니 CORS 이슈때문에 제대로 작동하지않았다.

클라이언트의 도메인과 서버의 도메인이 다르기 때문에 발생한 문제였고, 이를 해결하기 위해 몇가지 설정을 더 해주어야 했다. 다음글에서 CORS 이슈 해결방법을 이어서 포스팅하도록 하겠다.

profile
개발하면서 새로 배우거나 경험한 내용을 정리하고 그 외의 공부한 내용을 기록하는 곳입니다.

0개의 댓글