TIL

23/11/14 TIL __ 미들웨어로 로그인 인증하기 (access token)

GABOJOK 2023. 11. 14. 23:45

 

 

 

우리가 흔히 쓰는 로그인 기능.

어떤식으로 동작하고 있었을까?

 

다양한 방식이 있지만 오늘은  jwt를 사용한 access token 방식을 다뤄보려고 한다. 

이게 어떤건지 잘 모르겠다면, 아래의 글도 참고하면 좋을것 같다.

https://gabojok.tistory.com/207

 

 

🚗   JWT 발급하기

 

const token = await jwt.sign(
    { authorization: loginUserData.id }, 
    process.env.SECRET_KEY, 
    { expiresIn: '12h' }
)

 

 

이게 끝이다.!! 라이브러리를 왜 사용하는지 정말 이번 챕터에서 많이 느껴졌다.

딱봐도 간단해 보이지 않는걸, 아주 간단하게 만들어 주는 고마운 애들인것 같다.

 

나의 경우 회원 로그인 라우터 안에 만들어 주었고, 

회원이 입력한 데이터가 이상이 없을때에, jwt 토큰 발급을 진행해 주었다.

 

일단 jwt.sign 안에는 3개의 파라미터가 있다.

 

 

jwt.sign(payload, 시크릿키, 옵션, 콜백)

 

 

1️⃣  payload 

{ authorization: loginUserData.id }

 

payload부분은 객체 형태로 키 벨류로 넣어주면 된다.

키에는 지정하고 싶은 이름, 값에는 토큰으로 변환해서 넣어줄 값을 정해서 넣어주면 된다.

나의 경우 회원별 인증이 필요했기 때문에, 회원 각각의 고유한 아이디 넣어줬었는데,

회원이 로그인을 시도하면, 해당 회원에 대한 정보를 변수로 담아서 넣어줬다. (loginUserData)

그리고 그 변수에 아이디 값을 payload 부분에 넣어줬다.

 

 

2️⃣  시크릿키

process.env.SECRET_KEY

 

 

시크릿키 부분은 사실 그냥 문자열일 뿐이라서
"dkflekdlaup" 이렇게 시크릿키를 넣어줘도 된다.

(문자열만 올 수 있는건 아니고, 객체도 올 수 있다고 하는데, 아직 사용해 보지 않았다.)

 

시크릿키는 직접 임의로 만들면 되며, 아무래도 길수록 보안에 좋다.

 

그런데 이렇게 넣어주게 되면, 코드 공유가 일어나는 현업에서 정말 치명적일 수밖에 없다.

시크릿키가 공개적으로 깃에 올라가기 때문이다.

그래서 노출 되지 않도록 감싸주는게 일반적이다.

 

 

나의 경우 dotenv 라이브러리를 npm을 통해 설치했다.

이후,    .env  라는 파일을 vscode에서 만들어서, 그 안에 이런식으로 넣어뒀다.

SECRET_KEY = "dlfsakfeiowflkjsdlfkjsdlkdjwoijlkdeiuroiueoiwoqqowendlkn"

 

 

그리고 시크릿 키를 감춰 사용해야할 부분으로 돌아와서(지금의 경우 회원 로그인시 jwt발급하는 부분)

require('dotenv').config()

 

이 코드를 상단에 작성하고,

시크릿키를 작성해야 하는 부분에 

 

process.env.SECRET_KEY

 

이렇게 작성해 주면 된다.

 

이외에도, port, host, userName, password, dataBase 등등 노출이 우려스러운 정보들은

다 .env에 담아주는게 좋다.

 

 

 

 

3️⃣  옵션 

 

아직 콜백부분은 어떤방식으로 사용하는 건지 모르겠다.

그래서 우선 오늘은 옵션에 관한 부분을 말하려 한다. 

 

최하단에 적어둔 링크를 보면 알겠지만 생각보다 많은 옵션이 존재한다.

알고리즘 설정부터, expiresIn 혹은 notBefore를 통한 만료시간 설정 등등

 

가장 자주 사용될 만료 시간에 대해 먼저 말을하자면,

단순히 숫자만 적으면  초 단위로 계산되지만,

문자열로 적으면 해당 문자에 맞게 계산된다.

 

예를들면 "2 days" , "12h", "1m" 이런식으로 숫자와 단위를 적어주면 된다.

만약 "120" 이렇게 문자열 타입으로 숫자만 적어주게 되면 이는 밀리세컨 단위로 계산된다.

 

또한 알고리즘을 지정해서 사용하고 싶은 경우,

// sign with RSA SHA256
var token = jwt.sign({ foo: 'bar' }, privateKey, { algorithm: 'RS256' });

 

이런식으로 지정이 가능하다. 

기본 알고리즘 값은  HS256  이라고 하니 참고하자

 

 

 

 

 

이렇게 해서 만든 토큰을 token 이라는 변수에 담았다.

 

그럼 이제 res.cookie로 해당 토큰을 넣어주고,

클라이언트가 요청할때마다, cookie 를 주고받으며 토큰을 검사하는 시스템을 만들어 보자.

 

그러려면 일단 res.cookie로 토큰을 넣어줘야 한다.

 

 

🚌   JWT  쿠키에 넣어주기

 

res.cookie('authorization', `Bearer ${token}`)

 

cookie는 키/벨류 로 저장하기 때문에, 인자가 2개 필요하다.

그런데 값을 보면 Bearer 라는 걸 추가해 줬다.

이게 뭘까?

 

토큰을 쿠키에 넣어 보내줄때에, 우리는 인증 타입을 같이 적어줄 수 있다.

인증타입은 다양한데, 아래 블로그를 참고해 보면 좋을 것 같다. 

 

https://velog.io/@cada/%ED%86%A0%EA%B7%BC-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D%EC%97%90%EC%84%9C-bearer%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C

 

토근 기반 인증에서 bearer는 무엇일까?

본 글은 MDN - HTTP 인증, Veloport님의 게시글을 참고하여 작성되었습니다. 자세하게 알고싶으신 분은 해당 링크를 참고해주세요.토큰 기반 인증인증 타입마치며토큰 기반 인증은 쿠키나 세션을 이

velog.io

 

 

간단하게 말하면,

jwt 혹은 oauth에 대한 토큰을 사용할 때에 Bearer 인증 타입을 사용한다.

 

 

 

그리고 이제 미들웨어를 만들어 볼건데, 나의 경우 파일을 하나 따로 빼줬다.

그리고 그안에서 함수를 작성하여 module.exports 로 해당 함수를 내보내줬다.

 

 

 

 

🚌   인증한 회원인지 판별하는 미들웨어.

 

이부분에 대해 시작하기 전에 작동 원리를 잠깐 생각해 보자.

 

🧐 어디에 사용될것인가? 🧐

     지금 만들려고 하는 미들웨어는 유저가 상품에 대한 새로운 글을 작성하려고 할때 사용될 미들웨어이다.

 

🧐 유저는 자기가 인증했다는 사실을 어떻게 전달할 것인가? 🧐

     현재 백엔드 서버만 구현하고 있기 때문에, 

     유저가 새로운 글을 작성하려고 할 때 보내는 요청부분에서

     rest client 를 이용해서 직접 authorization 토큰 값을 넣어줘야 한다.

 

🧐 인증이 완료되면, 어디로 보내줘야 하나? 🧐  

     다시 라우터로 돌아가서 요청받은 기능을 수행해야 하기 때문에, 인증을 완료하면, 해당 유저 정보를 어딘가로 보내줘야 한다.

     나의 경우 res.locals로 보내줬다.

 

 

🧐 그럼 일련의 과정은 어떻게 돌아가는걸까? 🧐  

 

     👉 사용자가 로그인을 한다.

     👉  jwt 토큰이 발급된다.

     👉 사용자가 상품 작성기능을 사용하려고 한다.

     👉 /product 경로로 post 요청이 들어온다

     👉 해당 라우터가 작동하기 시작한다.

     👉 작동하면서 바로 인증 미들웨어를 만난다.

     👉 인증 미들웨어를 만난 순간 해당 함수로 이동해서 인증 미들웨어 함수 작동을 시작한다

     👉 해당 함수에서 jwt 변조 여부 검증 및 시크릿 키 확인을 한다.

     👉 이상이 없다면, 현재 받은 인증키에 해당하는 유저 정보를 찾아 res.locals.user로 담아주며 함수를 종료한다.

     👉 그럼 다시 /product 경로로 post 요청을 받는 라우터로 돌아오며, 인증받은 유저 정보를 가지고 있다.

     👉 이제 해당 라우터에서 유저가 작성한 정보를 받아서 product 필드에 넣어준다.

 

 

 

 

 

const express = require('express')
require('dotenv').config()
const jwt = require('jsonwebtoken')
const { UsersData } = require('../models')

async function middleware(req, res, next) {
  const { authorization } = req.headers
  const [authType, authToken] = authorization.split(' ')

  //타입이 bearer가 아니거나, authToken이 값이 없을 경우
  if (!authToken || authType !== 'Bearer') {
    return res.status(400).json({
      message: '토큰이 없습니다.',
    })
  }

  try {
    //토큰 변조여부 검증 & 시크릿 키 확인.
    const { authorization } = jwt.verify(authToken, process.env.SECRET_KEY)

    //토큰 유효기간 만료되면,
    if (!authorization) {
      return res.status(401).json({ message: '로그인을 해주세요.' })
    }

	//토큰에 해당하는 유저 정보
    const findUser = await UsersData.findOne({ where: { id: authorization } })
    if (!findUser) {
      return res.status(401).json({ message: '로그인을 해주세요.' })
    }

	//유저정보 담아두기
    res.locals.user = findUser

  } catch (error) {
    return res.status(401).json({
      message: '로그인을 해주세요.',
    })
  }
  next() 
}

module.exports = middleware

 

 

 

❗️❗️ 여기서 몇가지 주의할 점들이 있었다. ❗️❗️

1. 시퀄라이저를 사용해서 db를 관리하고 있다면, findOne메소드 안에 where 절로 조건을 추가해야 한다.

2. try 문 마지막에 return 을 적어주면 안된다.
(리턴을 적게 되면, 인증 다 해놓고, 거기서 끝내버린다. 다시 라우터로 돌아오지 않는다.)

3. 미들웨어의 경우 next() 를 호출하면 다음 바로 다음 미들웨어로 넘어갈 수 있다. 
인증 미들웨어의 경우 마지막에 next를 호출하지 않으면 기능이 작동하지 않았다.

 

 

 

 

강의로 개념을 익힐때보다, 실제로 구현해 보니 확실히 이해가 잘가는 것 같다.

항상 궁금했던 인증을 직접 구현할 수 있어서 기쁘다

 

 

 

 

참고한 사이트

https://github.com/auth0/node-jsonwebtoken?source=post_page-----49e211c65b45--------------------------------

 

GitHub - auth0/node-jsonwebtoken: JsonWebToken implementation for node.js http://self-issued.info/docs/draft-ietf-oauth-json-web

JsonWebToken implementation for node.js http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html - GitHub - auth0/node-jsonwebtoken: JsonWebToken implementation for node.js http://self-iss...

github.com

https://velog.io/@cada/%ED%86%A0%EA%B7%BC-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D%EC%97%90%EC%84%9C-bearer%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C