상세 컨텐츠

본문 제목

Express에서 JWT 토큰 인증 미들웨어 만들기 (Typescript 적용까지)

개발

by 호박너구리의 블로그 2021. 12. 22. 22:48

본문

- 종종 개발 삽질기를 보내드리고 있습니다. 간단하지만 재미있는 개발 삽질기를 받아보고 싶으시다면, 호박너구리 개발일지 뉴스레터를 구독해 주세요:)

 

api를 구현할 때 토큰을 자주 활용하게 됩니다.

 

현재 요청을 보낸 유저가 누구인지에 따라 보내줘야할 정보가 달라지기 때문인데요.

만약 자기의 정보를 요청하는 것이라면 정보를 보내줘도 되지만, 다른 유저의 정보를 요청하는 api 호출이라면 제한된 정보만 보내줘야 할 수도 있습니다. 

 

이렇듯 api 구현에 있어서 토큰 검증은 자주 사용되는데, 매번 똑같은 코드를 작성할 수는 없죠!

오늘은 토큰 검증을 위한 미들웨어 추가 방법에 대해 다루어보겠습니다:)

 

 

1. Express의 Middleware

Express에서 미들웨어 구현은 어렵지 않습니다. (관련 문서)

다음과 같이 app.use에 달아주면 모든 api 요청에 대해 동작을 수행하죠!

const express = require('express');
const app = express();

const myLogger = function (req, res, next) {
  console.log('LOGGED');
  next();
};

// 여기서는 logger가 middleware입니다.
// 모든 요청에 대해 로깅을 할 수 있죠
app.use(myLogger);

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

app.listen(3000);

 

 

2. 토큰 검증 미들웨어 추가하기

우리는 이제 이 부분을 토큰 검증 함수로 수정하기만 하면 됩니다.

const express = require('express');
const app = express();

const tokenChecker = function (req, res, next) {
  const userIdFromToken = JwtService.getUserIdFromRequest(req)
  req.userId = userIdFromToken
  next()
};

// 모든 api의 request에 userId가 undefined 혹은 string으로 들어가게 될 것입니다.
app.use(tokenChecker);

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

app.listen(3000);

 

실제 토큰 검증 함수는 JwtService.getUserIdFromRequest 인데요.

저는 request에서 토큰을 확인하여 verify하는 것을 하나의 함수로 만들어뒀습니다.

// JwtService.ts
// javascript로 써도 무방합니다
import { Request } from 'express';
import { User } from 'src/types';
import jwt from 'jsonwebtoken';

export default class JwtService {
  static getUserIdFromRequest = (req: Request): string | null => {
    const token = this.extractTokenFromRequest(req)
    if (!token) {
      return null
    }
    const jwtPayload = this.decodeJWT(token)
    return (jwtPayload as any)?._id || null
  }

  static extractTokenFromRequest = (req: Request): string | undefined => {
    const TOKEN_PREFIX = 'Bearer '
    const auth = req.headers.authorization
    const token = auth?.includes(TOKEN_PREFIX)
      ? auth.split(TOKEN_PREFIX)[1]
      : auth
    return token
  }

  static decodeJWT = (token: string) => {
    try {
      const decodedToken = jwt.verify(token, process.env.JWT_SECRET_KEY!)
      return decodedToken;
    } catch {
      return null
    }
  }

  static createJWT = async (user: User): Promise<string> => {
    const token = jwt.sign(
      { _id: user._id },
      process.env.JWT_SECRET_KEY!,
    );
    return token;
  };
}

 

 

3. 타입스크립트 오류 해결하기

만약 자바스크립트로 구현했다면 이제 더이상 할 것은 없습니다!

이미 모든 req에서 userId를 불러보면 (토큰이 제대로 담겼다면) 원하는 값이 찍히는 것을 확인하실 수 있을 것입니다.

 

그러나 typescript를 사용중이라면 아직 한 가지 작업이 더 남아있습니다.

 

왜냐하면 다음과 같은 에러가 뜰 것이기 떄문이죠

 

이는 타입스크립트에서 정의한 Request에 우리가 추가한 필드가 없기 때문입니다.

이를 해결하기 위해서는 namespace를 정의해야 하는데, 전혀 어렵지 않습니다!

 

우선 (type을 정의하는 디렉토리에) 자신이 원하는 파일을 만듭니다.

저는 types 폴더에 express.d.ts 라는 이름으로 만들겠습니다.

그리고 다음과 같은 코드를 적어주세요

declare namespace Express {
  export interface Request {
    userId: string | null
  }
}

 

그리고 tsconfig 파일에 들어가서 typeRoots 필드를 수정하면 끝입니다!

(원래 typescript 타입 파일이 모여있는 것과 방금 정의한 디렉토리를 읽도록 하는 것이죠)

{
  "compilerOptions": {
    ...
    "typeRoots" : ["./node_modules/@types", "./src/types"],
    ...
  }
}

 

이제 더욱 편하게 토큰을 활용하여 개발할 수 있겠죠?

그럼 오늘도 행복코딩하세요:)

728x90

관련글 더보기

댓글 영역