내가만든 쿠키(는 불량쿠키)

김장남·2023년 4월 11일
7
post-thumbnail

테오의 스프린트에서 만들었던 토이 프로젝트 '삿치'에 카카오로그인을 붙여 로그인 가능한 서비스로 만들려고 했다.

로그인을 구현하는 김에 JWT의 refresh token을 활용해보면 어떨까? 하는 마음에 검색을 시작했고
silent refresh라고 불리는 방법을 사용하기로 했다.

silent refresh는 access token을 javascript 변수에 두고 refresh token은 http only cookie에 두어 access token의 수명이 다하면 사용자가 눈치채지 못하게 access token을 교체해주는 방법이다. 새로고침 했을때도 cookie에 refresh token이 있으니 얼마든지 다시 로그인 상태로 만들 수 있는것이다.

보안상 access token을 local storage에 놓지 않는 것이 좋다는 이야기는 끊임 없이 들어지만 그렇다면 어떻게 관리를 해야 할지 몰라 못하고 있었고 이번이 기회다 싶어 refresh token 사용법을 익혀보자 생각해서 시작했는데 이렇게 많은 난관에 부딛힐 줄 몰랐고. 그 결과 끝내 해냈고 그 결과 백엔드와 프론트엔드 양쪽 시점에서 쿠키에 대한 지식을 많이 얻었고 나중에 활용하기 위해 여기 기록한다.

지인에게 쿠키에 대해서 설명 해드리다 보니 실제로 해보고 싶다고 하셔서 간단하게 실습해 볼 수 있는 간단한 프로젝트를 만들었습니다.
https://github.com/soorokim/cookie-study

Set-Cookie

MDN링크: Set-Cookie

위 링크의 MDN문서를 보면 다양한 옵션들이 존재한다.

Expires<Date>: 쿠키의 생존 수명이다. 설정하지 않으면 '세션쿠키'로 동작하게 되고 '세션쿠키'는 브라우저가 닫히면 사라진다. 형태는 Date형식이다.
Expires를 설정하면 설정된 기간까지 쿠키가 유지된다. 클라이언트의 시간에 상대적인 값으로 취급

Max-Age<number>: 쿠키의 만료시간을 초단위로 설정할수 있다. 0또는 음수가 지정되면 쿠키는 즉시 만료되고 ie6,7,8에서는 사용 할수 없다. Expires와 같은 기능을 하는 값, 함께 지정됐을때 Max-Age가 우선권을 갖는다.

Domain: 쿠키가 적용되어야 하는 호스트를 지정한다. 지정되어 있지 않으면 현재 URI를 기준으로 적용되고, 서브도메인은 포함하지 않는다. 백앤드의 domain이어야 한다. 사실 잘 모르겠다..ㅠ
ex) http://localhost:3000/auth/login -> localhost

Path: 쿠키 헤더를 보내기전에 리소스에 있어야 하는 URL경로를 나타낸다.
paths는 두가지 경우에 사용되는데
1. 브라우저 프론트엔드에서 백엔드로 요청을 보낼때 백엔드의 경로가 일치하면 request header에 싣어보낸다.
2. 프론트엔드에서 현재 uri의 path와 쿠키의 path가 일치할때 document.cookie에 접근하여 값을 가져온다.

ex) 만약 백엔드의 토큰 refresh할 때만 사용 할것이고
endpoint가 http://localhost:3000/auth/refresh 라면
path=/auth/refresh로 설정 하면된다. 참고로 path=/auth라고 적게되면 하위 경로를 모두 포함한다. (/auth, /auth/login, /auth/refresh)

!! 기본적으로 path는 어디서든 접근 할 수 있도록 "/"로 지정한다고 한다.

Secure: 보안쿠키들은 https프로토콜을 사용할때만 전송됩니다.

HttpOnly: httpOnly쿠키로 설정하게 되면 클라이언트 사이드의 자바스크립트에서 해당 쿠키에 접근 할 수 없습니다.

SameSite: 쿠키가 동작될 same site규칙입니다. CSRF에 대한 일부 보호를 제공합니다.(여기서 same site규칙은 same origin과 헷갈릴수 있는데 비슷하지만 조금 다릅니다. 아래서 설명하겠습니다.)
same-site은 lax, strict, none값으로 설정 할 수 있습니다.
strict인 경우에는
a사이트 -> a사이트 인 경우에만 쿠키를 전송하고
lax인 경우에는
b사이트 -> a사이트인 경우 몇가지 예외를 두어 쿠키를 전송할 수 있고
none인 경우에는
모든사이트에서 a사이트로 요청할때 쿠키를 전송합니다.
하지만 none인 경우에는 secure옵션을 반드시 필요로하고
secure옵션을 사용 하게되면 https 프로토콜로 요청해야합니다.

Same-Site와 Same-Origin

Google Developers링크: 동일사이트 및 동일 출처 이해하기

  • 주의: 브라우저는 공개접미사에 대한 쿠키설정을 거부한다.
    예를 들어 heroku에 업로드되어 xxx.herokuapp.com의 주소를 사용하는 클라이언트와 서버는 쿠키를 주고 받을 수 없다고 한다. stackoverflow 글

Same-Site 쿠키 설명

Google Developers링크: SameSite 쿠키 설명

추가 설명은 링크로 대체한다!

실전

백엔드 - nestjs

cors 설정해주기

  app.enableCors({
    origin: 'http://frontend-url.com',
    credentials: true,
  });

nest에서의 cors설정은 간단하게 이렇게 해 주면된다.
origin은 Access-Control-Allow-Origins값이고
credentials는 Access-Control-Allow-Credentials값이다.

프론트엔드에서 백엔드에 쿠키를 보내기위해서는 axios에서는 withCredential을 true로 설정해주어야하고 fetch에서는 credentials옵션을 true로 주게되는데 이때 백엔드에서 Access-Control-Allow-Origins값이 '*'(모든 오리진에 대한 접근 허용)로 설정되어있으면 아래와 같은 오류를 확인 할 수있다.

Access-Control-Allow-Credentials은 프론트에서 받는 증명정보를 포함한 요청을 허용하기위해서 설정 해준다. (자격증명: 쿠키, authorization 헤더들 또는 TLS 클라이언트 인증서)이를 위해서는 프론트엔드에서 withCredential을 true로 설정 해주어야 한다.

refresh token http-only 토큰으로 전송하기

cookie는 백엔드에서 로그인시 payload에 access token을 담아 응답하고 set-cookie를 사용해 httpOnly옵션과 path옵션을 사용해 응답객체에 추가해준다.

    res.cookie('refresh_token', refreshToken, {
      path: '/auth',
      httpOnly: true,
    });

추가 적으로 실무에서 사용하려면 백엔드와 프론트엔드 모두 ssl을 적용하여 https 환경으로 만들고 secure옵션을 주어야 할것 같다.

프론트엔드 - react

axios에서 withCredential 설정 해주기

 Axios.create({
      baseURL: 'http://example-backend-url',
      responseType: 'json' as const,
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiConfiguration.accessToken}`,
        }),
      },
      withCredentials: true, // 이 부분!
      timeout: 10 * 1000,
    });

프론트엔드에서 해줘야 할 설정은 간단하다.

결과물

로그인 요청시

httpOnly 쿠키로 잘 받아진다.

refresh 요청시

httpOnly 쿠키가 잘 전송되고 백엔드에서도 확인 가능하다!.

추가적으로

  • access token을 로컬스토리지에 넣지 않아도된다!
  • 심지어 로그인 유지를 하고싶어도 cookie에 Max-Age를 정해주게 되면 리프래시 토큰은 Max-Age까지 유지가 되고 그로 인에 언제든 다시 사이트에 접속해도 로그인 상태를 유지 할 수 있게 된다.
  • 휴 이제 백엔드 로직 만들면 된다...이제 로그인 끝이 보인다..ㅠㅠ 내가 뭐하는짓이지..
  • 그래도 너무 궁금하고 해보고싶었던것 정리를 끝마쳐서 너무 신이남

참고링크 정리

추가적으로 더 알고싶은것

  • 쿠키의 Path는 정확히 어떻게 동작하는가?
    - 의문이 생긴이유:
    프론트앤드(클라이언트) -> 백엔드(서버)로 http요청을 날릴때는 백엔드 경로에 따라 쿠키를 보낸다.
    개발자도구에서 쿠키를 확인 했을 때는 프론트앤드 경로로 동작하는 것 같아 보였다.(테스트 및 조사 필요)
profile
React 개발자

0개의 댓글