본문 바로가기
Web

[인증] JWT로 로그인 인증 1. 쿠키에 저장하기

by r4bb1t 2020. 8. 28.
반응형

로그인에 JWT를 사용할 경우, 아래와 같은 시나리오를 생각할 수 있다.

 

클라이언트에서 ID, Password로 로그인 요청을 보내면 서버에서 확인 후 토큰을 발급한다.
이 토큰이 "이 클라이언트가 로그인에 성공했음을 증명함" 증명서같은 역할을 하는 것이다.
그 이후에 클라이언트에서 요청을 할 때는 요청 헤더에 발급받은 토큰을 붙여서 보내면 서버에서는 해당 토큰이 유효한지만 검사하면 정보를 확인할 수 있다.

 

클라이언트에서 요청을 보낼 때 헤더에 토큰을 넣어서 보내려면, 토큰을 가지고 있어야 한다. 그러니까 어딘가에 저장해놓아야 한다는 뜻이다.

브라우저 메모리(react에서는 state)에 저장하면 한 번 렌더링될 때마다 다시 로그인을 해야 한다. local storage에 저장해놓으면, 따로 지우기 전까지는 클라이언트 쪽에 토큰이 계속 남아있는 데다 JS로 접근할 수 있어 보안에 취약하다🙄

여기저기 검색을 해보니 보통 httpOnly cookie에 저장해서 사용한다는 것을 알게 되었다.

 

쿠키몬스터. 귀엽다.

 

1. 그냥 쿠키로 저장하기

 

우선 백엔드 쪽 코드를 조금 수정해서, cookie-parser를 미들웨어로 등록한 후 로그인 POST 시

res.cookie("loginToken", token);

로 쿠키를 설정해주었다!

 

이런 식으로. 클라이언트 쪽에서는 react-cookie의 useCookies 훅을 사용해서

const [token, setToken, removeToken] = useCookies(["loginToken"]);

이렇게 하면 token.loginToken으로 슥삭💨 가져와서 헤더에 넣어줄 수 있겠지?

 

그런데 

쿠키가 애초에 세팅 자체가 안 되는 것이다. 로그인은 되는데 (response가 ok로 옴) 쿠키가 설정이 안 되는 게 대체 뭐가 문제인지 모르겠어서 며칠을 여기에 매달렸다.

 

이유가 너무 간단했다. cookie를 설정해주는 api 서버랑 클라이언트가 도메인이 달라서 그랬던 것이다. 클라이언트는 http://localhost:3000이고 서버는 http://localhost:4000이라서💢

 

var cors = require("cors");
const corsOptions = {
  origin: ["http://localhost:3000"],
  credentials: true,
};
app.use(cors(corsOptions));

 

서버 쪽에서 이렇게 origin과 credentials를 설정해주고,

 

const response = await fetch(`${process.env.REACT_APP_API_SERVER}/login`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    username: username,
    password,
  }),
  credentials: "include",
});

 

클라이언트에는 credentials: "include"로 fetch해주면

작동은 되는데

그러면 안된다.

 

2. httponly 쿠키로 저장하기

 

위처럼 하면

JS에서 접근이 가능하다. 그냥 개발자도구로도 수정할 수 있다. 딱 봐도 보안적으로 좋지 않아 보인다.

그래서 쿠키를 세팅할 때 httpOnly 옵션을 true로 줘야 한다.

 

그런데 httpOnly 쿠키는 JS에서 읽을 수가 없다. 그러니까 클라이언트에서 쿠키를 읽어다가 헤더에 넣는 게 안 된다는 뜻이다.

jwt 검사를 할 때 헤더가 아니라 쿠키를 검사하는 것으로 수정할 필요가 있다.

 

app.use(
  jwt({
    secret: process.env.JWT_SECRET,
    getToken: req => req.cookies.token
  })
);

 

일반적으로는 이렇게 하면 되고, 우리 백엔드는 passport를 사용해서 passport-jwt-cookiecombo를 이용할 예정이다. 그리고 쿠키의 크로스 도메인 문제를 방지하기 위해 클라이언트의 package.json에

{
  ...
  "proxy": "http://localhost:4000",
}

 

프록시를 추가해 주고, fetch에는

const response = await fetch(`/login`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    username: username,
    password,
  }),
  credentials: "include",
});

상대경로로 수정해준다.

반응형

댓글