Express로 JWT 인증을 구현한 API 서버 설계 튜토리얼

서론

Node.js는 비동기 이벤트 기반 JavaScript 런타임으로, 서버 사이드 개발에 널리 사용되고 있습니다. Express는 Node.js 플랫폼에서 구조화된 웹 애플리케이션을 작성할 수 있게 해주는 간단하고 유연한 웹 애플리케이션 프레임워크입니다. 오늘은 Express로 JWT(JSON Web Token) 인증을 구현하여 안전한 API 서버를 설계하는 방법을 소개하려고 합니다.

JWT는 웹 애플리케이션에서 클라이언트와 서버 간의 보안적인 데이터 전송을 가능하게 하는 토큰 기반 인증 방식입니다. 이 튜토리얼에서는 JWT를 활용하여 사용자 인증을 처리하는 방법을 단계별로 설명합니다. 이제 시작해 보겠습니다.

Node.js와 Express 설치

Node.js와 Express를 설치하고 설정하는 첫 단계는 매우 간단합니다. 먼저 Node.js를 설치해야 합니다. Node.js는 공식 웹사이트(https://nodejs.org/)에서 다운로드할 수 있습니다. 그 후, Express 애플리케이션을 생성하고 필요한 패키지를 설치하도록 하겠습니다.


# Node.js가 설치되어 있다면, 프로젝트 폴더를 생성하고 이동합니다.
mkdir jwt-auth-api
cd jwt-auth-api

# NPM을 이용해 새로운 Node.js 프로젝트를 초기화합니다.
npm init -y

# Express와 기타 필요한 패키지를 설치합니다.
npm install express body-parser jsonwebtoken

Express 서버 설정

Express 서버를 설정하려면 `express` 모듈을 불러오고, 서버의 기본 설정을 완료하면서 필요한 미들웨어를 추가해야 합니다. 다음은 기본적인 서버 설정 코드입니다.


const express = require('express');
const bodyParser = require('body-parser');

const app = express();
const PORT = process.env.PORT || 3000;

// Body-parser를 사용하여 요청 본문을 파싱합니다.
app.use(bodyParser.json());

app.get('/', (req, res) => {
    res.send('Hello, World!');
});

app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

이제 `node index.js` 명령어를 사용하여 서버를 실행하고, 웹 브라우저에서 `http://localhost:3000`에 접속하면 “Hello, World!”를 확인할 수 있습니다.


Express 서버 설정

Express 서버를 설정하는 과정은 비교적 간단합니다. 이 섹션에서는 Express를 설치하고 초기 설정을 완료하는 방법, 기본 미들웨어를 설정하는 방법, 그리고 라우팅을 설정하는 방법에 대해 다뤄보겠습니다.

2.1 Express 설치 및 초기 설정

먼저, Express를 설치하고 초기 설정을 완료해야 합니다. 아래 코드는 Express 애플리케이션의 초기 설정을 나타냅니다.


# NPM을 이용해 새로운 Node.js 프로젝트를 초기화합니다.
npm init -y

# Express와 기타 필요한 패키지를 설치합니다.
npm install express body-parser

이제 `index.js` 파일을 생성하고 Express 애플리케이션의 기본 설정을 아래와 같이 작성합니다.


const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) => {
    res.send('Hello, World!');
});

app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

위 코드는 기본적인 Express 서버의 초기 설정을 보여줍니다. 이 코드를 바탕으로 서버를 시작하고 간단한 “Hello, World!” 메시지를 출력할 수 있습니다.

2.2 기본 미들웨어 설정

Express에서는 미들웨어를 사용하여 HTTP 요청과 응답을 처리할 수 있습니다. 여기서는 `body-parser` 미들웨어를 사용하여 요청 본문을 JSON 형태로 파싱하도록 설정해 보겠습니다.


const bodyParser = require('body-parser');

// Body-parser를 사용하여 요청 본문을 JSON 형태로 파싱합니다.
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

위 코드는 `body-parser` 미들웨어를 설정하여, JSON 및 URL 인코딩된 데이터가 포함된 요청을 파싱하는 방법을 보여줍니다.

2.3 라우팅 설정

라우팅은 클라이언트 요청에 대해 서버가 어떻게 응답할지를 결정하는 중요한 부분입니다. 여기에서는 여러 라우트를 설정해 보겠습니다.


app.get('/', (req, res) => {
    res.send('Hello, World!');
});

app.post('/login', (req, res) => {
    const { username, password } = req.body;
    // 로그인 로직을 추가합니다.
    res.send(`Welcome, ${username}!`);
});

app.get('/profile', (req, res) => {
    // 프로필 정보를 반환합니다.
    res.send('This is the user profile.');
});

위 코드는 기본적인 GET, POST 요청을 처리하는 예제를 보여줍니다. 각 라우트에 대해 서버가 어떻게 응답할지를 설정할 수 있습니다.


JWT 토큰 설명 및 설정

JWT(JSON Web Token)는 클라이언트와 서버 간의 보안적인 데이터 전송을 가능하게 하는 토큰 기반 인증 방식입니다. 주로 사용자 로그인 및 인증에 사용되며, 정보를 안전하게 주고받을 수 있습니다.

3.1 JWT란 무엇인가?

JWT는 JSON 객체를 안전하게 URL을 통해 전송하기 위해 사용되는 JSON 기반의 개방형 표준입니다. JWT는 세 부분으로 구성됩니다.
1. 헤더(header): 알고리즘과 토큰 유형을 포함합니다.
2. 페이로드(payload): 토큰에 포함될 클레임 데이터를 담고 있습니다.
3. 서명(signature): 비밀 키를 이용해 헤더와 페이로드를 암호화한 부분입니다.

이 세 부분은 점(.)으로 구분되어 하나의 문자열을 형성합니다.

3.2 JWT를 활용한 인증 방식

JWT를 활용한 인증 방식은 일반적으로 다음과 같은 단계로 이루어집니다.
1. 사용자 로그인: 클라이언트가 사용자 인증 정보와 함께 서버에 로그인 요청을 보냅니다.
2. 토큰 생성: 서버는 사용자 인증 정보를 확인한 후 JWT를 생성하여 클라이언트에게 반환합니다.
3. 요청 포함: 클라이언트는 이후 요청에서 이 JWT를 포함하여 서버에 요청을 보냅니다.
4. 검증 및 응답: 서버는 수신된 JWT를 검증하고 요청을 처리합니다.

3.3 JWT 생성 및 검증

이제 JWT의 생성과 검증 과정을 코드로 구현해보겠습니다. 먼저 `jsonwebtoken` 패키지를 설치해야 합니다.


# jsonwebtoken 패키지 설치
npm install jsonwebtoken

이제 JWT를 생성하고 검증하는 코드를 작성해보겠습니다.


const jwt = require('jsonwebtoken');
const secretKey = 'your-256-bit-secret';

// JWT 생성 함수
function generateToken(user) {
    const payload = {
        username: user.username,
        id: user.id,
        role: user.role
    };
    return jwt.sign(payload, secretKey, { expiresIn: '1h' });
}

// JWT 검증 미들웨어
function verifyToken(req, res, next) {
    const token = req.headers['authorization'];
    if (!token) {
        return res.status(403).send('A token is required for authentication');
    }
    try {
        const decoded = jwt.verify(token, secretKey);
        req.user = decoded;
    } catch (err) {
        return res.status(401).send('Invalid Token');
    }
    return next();
}

// 사용자 로그인 처리
app.post('/login', (req, res) => {
    const { username, password } = req.body;
    // 실제 인증 로직을 여기에 추가합니다.
    const user = { id: 1, username: 'john_doe', role: 'admin' };
    const token = generateToken(user);
    res.status(200).json({ token });
});

// 인증이 필요한 라우트에 미들웨어 추가
app.get('/protected', verifyToken, (req, res) => {
    res.send('This is a protected route');
});

위 코드는 JWT를 생성하고 검증하는 함수 및 미들웨어를 구현하는 예제입니다. 로그인 엔드포인트에서 JWT를 생성하여 클라이언트에 반환하고, 이후 보호된 라우트에서 토큰을 검증하여 접근을 허용합니다.


API 엔드포인트 구성

여기서는 회원가입, 로그인, 그리고 인증된 사용자만 접근 가능한 API 엔드포인트를 구성하는 방법에 대해 알아보겠습니다.

4.1 회원가입 API

회원가입 엔드포인트는 사용자가 입력한 정보를 바탕으로 새로운 계정을 생성합니다. 예제에서는 간단한 사용자 정보 검증을 포함합니다.


const express = require('express');
const bodyParser = require('body-parser');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');

const app = express();
const users = []; // 예제용 사용자 데이터 저장소
const secretKey = 'your-256-bit-secret';

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

// 회원가입 엔드포인트
app.post('/register', async (req, res) => {
    const { username, password } = req.body;

    // 사용자가 이미 존재하는지 확인
    const userExists = users.find(user => user.username === username);
    if (userExists) {
        return res.status(400).json({ message: 'User already exists' });
    }

    // 비밀번호 해싱
    const hashedPassword = await bcrypt.hash(password, 10);
    const newUser = { id: users.length + 1, username, password: hashedPassword, role: 'user' };
    users.push(newUser);

    res.status(201).json({ message: 'User registered successfully' });
});

4.2 로그인 API

로그인 엔드포인트는 사용자가 입력한 인증 정보를 바탕으로 JWT를 생성하여 반환합니다.


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

    // 사용자 존재 여부 확인
    const user = users.find(user => user.username === username);
    if (!user) {
        return res.status(400).json({ message: 'Invalid credentials' });
    }

    // 비밀번호 검증
    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
        return res.status(400).json({ message: 'Invalid credentials' });
    }

    // JWT 생성
    const token = jwt.sign({ id: user.id, username: user.username, role: user.role }, secretKey, { expiresIn: '1h' });

    res.status(200).json({ token });
});

4.3 인증된 사용자만 접근 가능한 API

여기서는 JWT 미들웨어를 사용하여 인증된 사용자만 접근 가능한 보호된 엔드포인트를 설정합니다.


const verifyToken = (req, res, next) => {
    const token = req.headers['authorization'];
    if (!token) {
        return res.status(403).json({ message: 'A token is required for authentication' });
    }
    try {
        const decoded = jwt.verify(token, secretKey);
        req.user = decoded;
    } catch (err) {
        return res.status(401).json({ message: 'Invalid Token' });
    }
    return next();
};

// 인증된 사용자만 접근 가능한 엔드포인트
app.get('/dashboard', verifyToken, (req, res) => {
    res.status(200).json({ message: 'Welcome to the dashboard', user: req.user });
});

위 코드는 JWT를 통해 사용자 인증을 구현하고, 보호된 엔드포인트에 미들웨어를 적용하여 인증된 사용자만 접근할 수 있도록 설정하는 방법을 보여줍니다.


테스트 및 배포

이번 섹션에서는 Postman을 사용해 API를 테스트하고, 서버를 배포하는 방법에 대해 알아보겠습니다.

5.1 Postman을 이용한 API 테스트

Postman은 API를 테스트하는 데 유용한 도구입니다. 여기서는 회원가입, 로그인, 그리고 보호된 엔드포인트에 대해 테스트를 해보겠습니다.

1. **회원가입 테스트:**
– Postman을 열고 ‘POST’ 요청을 선택합니다.
– URL에 `http://localhost:3000/register`를 입력합니다.
– ‘Body’ 탭에서 ‘raw’를 선택하고 JSON 형태로 다음과 같은 데이터를 입력합니다:
“`json
{
“username”: “testuser”,
“password”: “testpass”
}
“`
– ‘Send’ 버튼을 누르면 사용자 등록이 완료됩니다.

2. **로그인 테스트:**
– ‘POST’ 요청을 선택하고 URL에 `http://localhost:3000/login`을 입력합니다.
– ‘Body’ 탭에서 ‘raw’를 선택하고 JSON 형태로 다음과 같은 데이터를 입력합니다:
“`json
{
“username”: “testuser”,
“password”: “testpass”
}
“`
– ‘Send’ 버튼을 누르면 JWT 토큰이 반환됩니다.

3. **보호된 엔드포인트 테스트:**
– ‘GET’ 요청을 선택하고 URL에 `http://localhost:3000/dashboard`를 입력합니다.
– ‘Headers’ 탭을 선택하고 새로운 헤더를 추가합니다:
“`
Key: Authorization
Value: Bearer {your_token_here}
“`
– {your_token_here} 부분에 로그인 API에서 받은 JWT 토큰을 넣습니다.
– ‘Send’ 버튼을 누르면 보호된 데이터가 반환됩니다.

5.2 서버 배포하기

서버를 배포하는 방법에는 여러 가지가 있습니다. 여기서는 Heroku를 사용하여 Express 앱을 배포하는 방법을 설명합니다.

1. **Heroku CLI 설치:**


   # macOS 설치
   brew tap heroku/brew && brew install heroku

   # 로그인
   heroku login
   

2. **프로젝트 준비:**


   # Git 초기화
   git init

   # Heroku 앱 생성
   heroku create my-awesome-app

   # Procfile 생성 (루트 디렉토리에)
   echo "web: node index.js" > Procfile
   

3. **변경 사항 커밋 및 푸시:**


   # 파일 추가
   git add .

   # 커밋
   git commit -m "Initial commit"

   # Heroku에 푸시
   git push heroku master
   

4. **환경 변수 설정:**


   heroku config:set SECRET_KEY=your-256-bit-secret
   

5. **배포 확인:**


   # 브라우저에서 앱 열기
   heroku open
   

위 단계를 따르면 Heroku에 서버 배포가 완료되고, 제공된 URL을 통해 API에 접근할 수 있습니다.


Leave a Comment