JWT & express 를 이용하여 로그인 구현하기

rlorxl·2022년 9월 6일
4

Javascript

목록 보기
11/12

프로젝트를 진행하면서 로그인을 구현하게 되었다. 평소에 항상 뒷전으로 밀리는 느낌이라 마음에 걸렸었는데 이번에 제대로 공부할 시간이 생겨서 잊지않도록 기록을 남겨두려한다.

JWT의 개념

JWT는 JSON Web Token의 줄임말로 암호화된 3가지 데이터를 이어붙인 토큰이다. 토큰은eyJhbGciOi...어쩌구 저쩌구 하는 알아보지 못할 알파벳들이 나열된 것처럼 보이는데 이것을 발행하여 유저를 인증하고 식별하는 인증을 할 수 있다.

보통 인증방식은 세션방식을 많이 사용했는데 세션방식은 세션ID를 클라이언트(브라우저)와 메모리에 저장해서 요청을 보낼 때 양쪽의 세션ID를 비교하는 방식으로 사용된다. 세션 방식보다 JWT가 나은 점은 사용자 인증에 필요한 모든 정보(Claim)는 토큰 자체에 포함하기 때문에 별도의 인증 저장소가 필요없다는 점이다. DB에 사용자 정보를 보관, 관리하지 않고 서버를 통하지 않을 경우 정보를 수정할 수 없기 때문에 관리에 용이하며 트래픽 부담도 낮다.

JWT의 단점은 생성된 토큰은 만료되지 않고(기간을 정할수는 있음) 역시 토큰의 정보를 서버에서 관리하고 있지 않는다는 점 때문에 토큰이 유출될 경우 통제하기가 어렵다는 점이 있다. 그렇기 때문에 토큰의 payload에는 유출되어도 괜찮은 정보들로 구성하는게 낫다.

이런 단점을 보완하기 위해 토큰을 발급할 때 기간이 짧은 액세스 토큰과, 기간이 긴 리프레시(refresh)토큰을 두개 발급할 수 있다. 리프레시 토큰은 DB에 저장되어 관리되고 유출된 토큰을 DB에서 삭제해서 토큰 갱신을 못하도록 할 수 있다. 액세스 토큰의 수명이 다하면 리프레시 토큰과 db에 저장된 값을 대조해서 맞으면 새 액세스 토큰을 발급한다. 하지만 유출된 액세스 토큰의 만료시간까지는 강제로 만료시킬 수 없다.


로그인 구현하기

1. 환경세팅

1-1. 라이브러리 세팅

구현에 앞서 먼저 환경세팅이 필요하다. 필요한 라이브러리들은 다음과 같다.

  • express - Node.js 웹 애플리케이션 프레임워크로 간편하게 웹서버를 구축 할 수 있고 여러 미들웨어를 사용할 수 있다.
  • dotenv - 환경 변수를 파일에 저장할 수 있도록 해주는 라이브러리. 현재 디렉토리에 위치한 .env파일에 접근할 수 있도록 도와준다.
  • jsonwebtoken - JWT를 쉽게 사용하도록 지원하는 모듈. 토큰 암호화 및 발급 인증 등을 지원한다.
  • cookie-parser - 요청과 함께 들어온 쿠키를 해석하여 req.cookies객체로 만든다.
  • nodemon - 디렉토리의 파일 변경이 감지되면 노드 응용 프로그램을 자동으로 다시 시작하도록 해준다.

npm init -y

npm install express --save dotenv jsonwebtoken cookie-parser

npm install --save-dev nodemon

1-2. 폴더 구조

📦 Authentication
└─📂 public
  ├─📄 index.html
  ├─📄 welcome.html
  ├─📂 src
  │ ├─📂 middleware
  │ │ └─📄 cookieJwtAuth.js
  │ ├─📂 routes
  │ │  ├─📂 auth.js
  │ │  └─📂 login.js
  ├─🛠 .env
  └─🖥 server.js

1-3. .env파일

환경변수의 Key로 node.js에서 제공하는 crypto 모듈을 이용해서 랜덤 스트링을 .env파일에 설정한다.

require('crypto').randomBytes(64).toString('hex')

// .env
ACCESS_TOKEN_SECRET=965bb5f7e73a8cdd2fd64a9d894e26a790e4ea2c6696b6a4ecea2cb2dfc658bb576e744d86afa4ee99b0d8d252b11fd6ced4fe0e9a0014dea08da69cb450f767

2. server.js

server.js파일은 설치한 패키지들을 지정하고, local포트와 연결하고, 들어온 요청에 대한 처리를 담당한다.

// server.js
const express = require('express');
const cookieParser = require('cookie-parser');
const path = require('path');
require('dotenv').config();
const app = express();

const setUpLoginRoute = require('./src/routes/login');
const setUpAddRoute = require('./src/routes/add');
const PORT = 4001;

/* 
- form으로 제출되는 값은 x-www-form-urlencoded 형태이므로 express.json()으로는 값을 해석할 수 없다.
- extended 옵션 정의
extended: false -> NodeJs에 기본으로 내장된 querystring모듈을 사용.
extended: true -> 추가로 설치가 필요한 qs모듈을 사용.
false로 지정하면 오류발생.
*/
app.use(express.static('public'));
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());

// 주소창에 경로를 입력했을 때의 처리
// root route (path가 '/'면 ./public/main.html파일을 가져온다.)
app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'public/index.html')); 
  // path.join(): 여러 인자를 하나의 경로로 합쳐준다.
  // __dirname: (directory name)현재 js파일의 경로가 저장되어있다.
});

app.get('/welcome', (req, res) => {
  res.sendFile(path.join(__dirname, 'public/welcome.html'));
});

setUpLoginRoute.login(app);
setUpAddRoute.add(app);

app.listen(PORT, () => {
  console.log(`Example app listening att http://localhost:${PORT}`);
});

3. login.js

index의 form action 경로를 '/login'으로 설정해서 /login으로 요청이 가도록 설정하고 req.body에서 username과 password를 받아서 기존에 있던 유저정보와 비교한다. 패스워드가 따르면 401에러를 띄우고 성공하면 jwt토큰을 생성하고 쿠키에 토큰을 설정하고 /welcome으로 리다이렉트를 보낸다.

// login.js
const jwt = require('jsonwebtoken');

const getUser = username => {
  return { username: 'zoala', password: '456456' };
};

const login = app => {
  app.post('/login', (req, res) => {
    const { username, password } = req.body;

    const user = getUser(username);

    if (user.password !== password) {
      return res.status(401).send({ error: 'please check your password.' });
    }

    console.log('login success');

    // ** 중요
    const token = jwt.sign(user, process.env.MY_SECRET, { expiresIn: '1h' }); // 토큰 생성

    res.cookie('token', token); // 쿠키에 토큰 설정

    return res.redirect('/welcome'); // redirect: new request(get) 해당 url로 GET요청을 자동으로 해줌.
  });
};

module.exports = { login };

4. add.js

welcome페이지에서 /add요청이 있을 때 인증을 담당하는 함수이다. 미들웨어 함수는 쿠키에서 토큰을 받아 토큰인증을 처리하고(jwt.verify) 토큰이 유효할시 next()함수를 호출하여 다시 /welcome으로 리다이렉트를 보내준다. 실패하면 루트 페이지로 보내도록 되어있다.

// add.js
const { cookieJwtAuth } = require('../middleware/cookieJwtAuth');

const add = app =>
  app.post('/add', cookieJwtAuth, (req, res) => {
    console.log(req.user); 
    res.redirect('/welcome'); 
  });

module.exports = { add };

📍인증이 성공했을 때 콘솔에 뜨는 유저정보. iat와 exp가 포함되어 있다.

{
  username: 'zoala',
  password: '456456',
  iat: 1662702285, // 토큰이 발급된 시간
  exp: 1662705885 // 토큰 만료시간
}

middleware

// cookieJwtAuth.js
require('dotenv').config();
const jwt = require('jsonwebtoken');

exports.cookieJwtAuth = (req, res, next) => {
  const { token } = req.cookies;
  try {
    const user = jwt.verify(token, process.env.MY_SECRET); // 검증
    req.user = user;
    next();
  } catch (err) {
    res.clearCookie('token');
    return res.redirect('/');
  }
};

이렇게 jwt를 사용하면 토큰의 생성과 인증이 간단해져 간단하게 로그인구현이 가능하다!
여기서 생성하는것은 액세스토큰이기 때문에 기간을 짧게 설정하거나 로그인 요청시 refresh토큰도 함께 설정해서 db에 저장하고 refresh토큰이 있다면 액세스토큰을 재발급할 수도 있지만 db가 없기 때문에 액세스토큰 발급,인증까지만 구현되었다.


reference

Learn JWT in 10 Minutes with Express, Node, and Cookie Parser

0개의 댓글