Node.js

[Node.js] 로그인 api 만들기 + JWT 활용

코드비버 2025. 3. 4. 01:38

이전 포스팅에서 로그인 기능을 구현하는 방법에 대해 다루었다.

리액트에서 ID, PW 입력을 받아 로그인 버튼을 누르면 로그인 api를 호출하는데 이번 포스팅에선 이 api를 다룰 것이다.

이전에 생성해둔 server.js를 사용할 것이다.

 

https://duski96.tistory.com/15

 

[Node.js] React에 MySQL 연동을 위한 Server 생성

React에 MySQL 연동하기 포스팅에 작성했던 내용인데, 내용이 너무 길고 리액트와 연관이 큰 부분은 아니라 새로운 카테고리의 게시글로 분리했다. Server 생성 과정DB로부터 데이터를 불러오기 위

duski96.tistory.com

 

authRouter.js 파일 생성

server 폴더 구조 및 authRoutes.js 코드 일부

 

폴더 구조는 동일하지만 routes 폴더의 index.js 파일을 authRoutes.js 파일로 바꿨다.

인증과 관련된 api 파일이라는 점을 직관적으로 알 수 있고 추후 다른 종류의 api를 추가할 때 어느정도 코드를 분리해 관리하기 위함이다.

 

server.js 세팅

// server.js

// node.js 환경에서는 import.meta.env 사용 X
import dotenv from 'dotenv';
import path from 'path';
dotenv.config({path:path.resolve(process.cwd(), '../.env')}); //env 파일 경로 지정

// express 모듈 호출
import express from 'express';
const app=express();

// HTTPOnly cookie 사용
import cookieParser from 'cookie-parser';
app.use(cookieParser());

// POST 방식으로 받은 데이터를 읽기 위한 미들웨어 추가
app.use(express.json());

// /라우터 파일에서 api 처리
import authRouter from './routes/authRouter.js';
app.use('/api/auth', authRouter);

const PORT=process.env.VITE_PORT || 4000;

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

 

우선 server.js 파일에서 일부 내용을 수정했다.

authRouter.js 파일을 불러오는 경로를 '/api/auth'로 변경했고 추후 token 인증 과정에 필요한 cookieParser를 추가했다.

또한 POST 방식으로 전달받은 데이터를 파싱하기 위해 express.json() 미들웨어를 추가해주었다.

 

로그인 api 생성

# jsonwebtoken 설치

npm i jsonwebtoken
// authRouter.js

import express from 'express';
const router = express();
import db from '../config/db.js';
import jwt from 'jsonwebtoken';

// 로그인 api
router.post('/login', (req, res)=>{ 
    // 사용자가 input 태그에 입력한 Id와 Pw
    const userId=req.body.userId;
    const userPw=req.body.userPw;

    // 클라이언트에서 유저의 상태를 관리할 수 있도록 token에 저장할 데이터 지정
    const sql='select user_id, nickname, email, car from users where user_id=? and user_pw=?';

    db.query(sql, [userId, userPw], (err, data)=>{
        if(err){ // DB 에러
            res.send(err);
        }
        else if(!data.length){ // sql 실행 결과 data가 없는 경우 -> 로그인 실패
            const idSql='select user_id from users where user_id=?';
            db.query(idSql, [userId], (err, data)=>{
                if(data.length){
                    // idSql 실행 결과 data가 있는 경우 -> 입력한 id는 DB에 존재함 -> pw 오류
                    res.status(401).send({message:'wrongPw'});
                }
                else{
                    // idSql 실행 결과 data가 없는 경우 -> DB에 id가 없음 -> id 오류
                    res.status(401).send({message:'wrongId'});
                }
            });
        }
        else{ // id, pw 모두 맞게 입력함
            const userInfo=data[0];

            // jwt 토큰 생성
            const token=jwt.sign({userId:userInfo.user_id, nickname:userInfo.nickname, email:userInfo.email, car:userInfo.car}, process.env.JWT_SECRET, {expiresIn:'1h'});
            // httpOnly 쿠키에 저장 후 클라이언트에 토큰 전달
            res.cookie('token', token, {httpOnly:true, secure:true, sameSite:'Strict'});
            res.send(token);
        }
    });
});

 

로그인 api는 클라이언트로부터 전달받은 ID, PW 값을 활용해 DB에 있는 데이터와 대조하는 역할을 할 것이다.

SQL문은 각자의 프로젝트에 맞게 필요한 데이터를 조회할 수 있도록 작성해주고 query() 매서드를 활용해 작동 결과에 따라 결과를 반환해주면 된다.

 

api 동작 방식

▶ 로그인에 실패한 경우

DB 에러가 발생한 경우 클라이언트에게 query() 매서드가 자동으로 생성하는 err값을 전달해준다.

만약 유저의 실수로 로그인에 실패했을 경우 HTTP 상태 코드 401과 함께 클라이언트에서 ID와 PW 중 어느것을 잘못 입력했는지 파악할 수 있도록 임의의 객체를 생성해 전달하도록 만들었다.

 

▶ 로그인에 성공한 경우

유저가 입력한 ID와 PW를 모두 만족하는 data를 찾아 userInfo 변수에 저장해준다.

(참고로 data는 배열 형식으로 반환된다.)

이제 userInfo에 저장된 데이터를 가지고 JWT(JSON Web Token)를 생성해 HTTP Only Cookie로 저장한다.

 

JWT가 뭔데?

간단하게 설명하자면 서버에서 클라이언트로 전달할 데이터(객체)를 암호화 시킨 토큰으로, 아주 긴 문자열이다.

JWT를 사용하면 사용자의 로그인 상태를 서버가 아닌 클라이언트에서 확인할 수 있으므로 서버에 부담이 적어진다는 장점이 있다.

사실 이 외에 설명할 점이 많긴 하지만 포스팅하려면 분량이 너무 길어지기 때문에 지금은 공식 홈페이지의 소개글로 대체하고 추후 기회가 되면 다뤄야 할 것 같다.

 

https://jwt.io/introduction

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 

JWT 생성하기

# node.js 터미널에서 비밀키 생성

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
// jwt 토큰 생성
const token=jwt.sign({/* token에 저장할 정보 */}, process.env.JWT_SECRET, {/* 만료 시간 */});

// httpOnly 쿠키에 저장 후 클라이언트에 토큰 전달
res.cookie('token', token, {httpOnly:true, secure:true, sameSite:'Strict'});
res.send(token);

 

jwt.sign() 매서드에 저장할 객체, 비밀키, 만료시간을 파라미터로 전달해 생성한다.

참고로 비밀키는 터미널에서 임의의 문자열을 생성하면 되고, 반드시 .env 파일에 저장하고 참조해서 사용해야 한다.

 

JWT 인증

// authRouter.js

// 토큰 인증을 위한 미들웨어
const verifyToken=(req, res, next)=>{
    // HTTPOnly cookie에 저장된 토큰 값
    const verifyToken=req.cookies.token;

    if(!verifyToken) return res.status(401).send({message:'인증 토큰이 필요합니다.'});

    try{
        // jwt로 암호화된 user 정보를 복호화
        const decoded=jwt.verify(verifyToken, process.env.JWT_SECRET);
        req.user=decoded; 
        next(); // 다음 미들웨어 실행
    }
    catch(error){
        return res.status(401).send({message:'토큰이 유효하지 않습니다.'});
    }
}

// 인증 api
router.get('/verify', verifyToken, (req, res)=>{
    // 클라이언트에게 요청했던 데이터 중 유저의 정보를 다시 전달
    res.send(req.user);
});

 

jwt.verify() 매서드는 토큰의 유효성을 검증하고 토큰에 담긴 정보를 디코딩하는 기능을 제공한다.

사용자가 권한이 필요한 페이지에 접근할 경우 verify api를 통해 토큰이 유효한지 검사 후, 토큰에 저장된 정보를 사용자가 확인할 수 있는 형태로 바꿔준다.

이렇게 복호화된 데이터는 인증 api를 통해 클라이언트에게 전달된다.

 

https://duski96.tistory.com/16

 

[React] 로그인 구현하기

이전에 커뮤니티 기능을 위해 MySQL과 리액트를 연동하는 방법을 포스팅했다.이제 가장 먼저 구현해볼 것은 로그인 기능이다.React(클라이언트)에서 입력한 ID와 PW를 Server로 넘겨 SQL 쿼리문으로 해

duski96.tistory.com