[Apis] redirect에 cookie를 담아보아요 - 실험

Jchan·2023년 6월 22일
1

이 전 포스트에 이어 본격적으로 여러 실험을 해보겠습니다.

0-1. 우선 코드부터 살펴보자!

서두가 길었습니다. 빠르게 코드를 살펴보겠습니다.

우선 저희 서버는 NestJS입니다.

Nest에는다양한 요청을 세분화하여 처리하는 Controller 속 route handler들이 있습니다.

이 라우트 핸들러에서 redirect와 쿠키를 입히는 작업을 수행할 수 있습니다.

@Get(`kakao/redirect`)
  async signInByKakao(
    @Query(ValidationPipe) signInByKakaoDto: SignInByKakaoDto,
    @Res() res: Response,
  ) {
    const { accessToken, refreshToken } = await this.authService.signInByKakao(
      signInByKakaoDto,
    );
    Logger.log('User sign in', 'AuthController');
    return res
      .cookie(TOKEN.REFRESH_TOKEN, `Bearer ${refreshToken}`, {
        maxAge: MAX_SIZE.REFRESH_TOKEN_MAX_AGE,
        httpOnly: true,
        sameSite: 'none',
        secure: true,
      })
      .cookie(TOKEN.ACCESS_TOKEN, `Bearer ${accessToken}`, {
        maxAge: MAX_SIZE.ACCESS_TOKEN_MAX_AGE,
        sameSite: 'none',
        secure: true,
      })
      .redirect(process.env.FRONT_URL); // default 302
  }

현재 서버 상태 :

현재 BE와 FE의 도메인이 달라 "cross-origin" 상태

Access-Control-Allow-Origin=*

Access-Control-Allow-Credentials=true

쿠키는 secure, httpOnly, secure를 사용하고 있는 상태



0-2. 요청과 응답 결과

위의 코드대로 요청과 응답을 한 번 살펴 보겠습니다.

// Request HTTP Headers
GET /auth/kakao/redirect HTTP/1.1
...
Host: [서버 도메인]
...
Referer: https://accounts.kakao.com/ -> 링크가 있던 곳
...
HTTP/1.1 302 Found
... 
Access-Control-Allow-Credentials: true
Set-Cookie: token1=Bearer%[UUID]; Path=/; Expires=Thu, 29 Jun 2023 08:20:47 GMT; HttpOnly; Secure; SameSite=None
Set-Cookie: token2=Bearer%[UUID];; Max-Age=18000; Path=/; Expires=Thu, 22 Jun 2023 13:20:47 GMT; Secure; SameSite=None
Location: [클라이언트 도메인]
...

여기서 이상한 현상은 쿠키 스코프가 프론트 도메인이 아닌 서버 도메인에 잡힌다는 것입니다.

이렇게 되면 프론트에서 httpOnly가 아님에도 불구하고 쿠키를 열어볼 수 없게 됩니다.

하지만 쿠키가 set-cookie가 되어있기 때문에 쿠키 전송 자체는 아무 문제없이 잘 됩니다.

또한 safari 브라우저, 시크릿 모드 등은 쿠키가 전송 되지 않습니다.

찾아보니...

사파리는 기본적으로 크로스 사이트 추적을 막기 때문에 크로스 사이트 환경에서는 쿠키 설정이 한정되어 있습니다 -> 사파리때문에 답은 이미 정해져 있네요;;


1. 영속적인 redirect로 바꿔보기

MDN에 나온 redirect 정리로는 영속적인 redirect의 사용이 필요해 보였습니다.

지금 서버는 적절하지 않은 redirect 방법을 사용하고 있는 것 같은데요.

영속적인 redirect를 통해 쿠키 스코프를 프론트에 지정할 수 있지 않을까요?

Express에서 redirect를 default 값인 302 FOUND로 응답을 하는데 이를 301 Moved Permanently로 응답해보겠습니다.

...
return res
      .cookie(TOKEN.REFRESH_TOKEN, `Bearer ${refreshToken}`, {
        maxAge: MAX_SIZE.REFRESH_TOKEN_MAX_AGE,
        httpOnly: true,
        sameSite: 'none',
        secure: true,
      })
      .cookie(TOKEN.ACCESS_TOKEN, `Bearer ${accessToken}`, {
        maxAge: MAX_SIZE.ACCESS_TOKEN_MAX_AGE,
        sameSite: 'none',
        secure: true,
      })
	  // 302 -> 301
      .redirect(HttpStatus.MOVED_PERMANENTLY, process.env.FRONT_URL);
  }

redirect 메서드 수정 후 실험 결과

아쉽게도 아직도 쿠키 도메인이 서버로 잡힙니다...
하지만 영속적인 redirect를 해서 그런지 partition key는 카카오에서 서버로 변환된 것을 확인 가능합니다.

redirect가 아닌 상황에서 실험 결과

redirect의 문제가 아니었을까요? redirect가 아닌 일반 Get 환경에서 테스트 해보겠습니다.

@Get('testtest')
  tttest(@Res() res: Response){
    return (
      res
        .cookie(TOKEN.ACCESS_TOKEN, `Bearer asdf`, {
          maxAge: MAX_SIZE.ACCESS_TOKEN_MAX_AGE,
          sameSite: 'none',
          secure: true,
        })
        .cookie(TOKEN.REFRESH_TOKEN, `Bearer asdf`, {
          maxAge: MAX_SIZE.REFRESH_TOKEN_MAX_AGE,
          httpOnly: true,
          sameSite: 'none',
          secure: true,
        })
    );
  }

redirect 상황이 아님에도 불구하고 쿠키 스코프가 서버 도메인에 잡힙니다. (도메인 이전으로 인해 서버 도메인 이름이 바뀌었습니다.)

따라서 redirect와 상관없이 cross-origin 상황이 문제라는 것을 유추해 볼 수 있었습니다.



2. 쿠키 스코프를 건드려 보자

범인은 redirect가 아닌 것 같다. 그러면 누가 문제인가...

cross-origin 상황에 경우, 자동으로 쿠키의 스코프가 서버 도메인으로 할당되는 것을 확인하실 수 있으셨습니다.

그렇다면 쿠키 domain을 명시해주면 되지 않을까요?

return (
      res
        .cookie(TOKEN.ACCESS_TOKEN, `Bearer ${accessToken}`, {
          maxAge: MAX_SIZE.ACCESS_TOKEN_MAX_AGE,
          sameSite: 'none',
          secure: true,
          domain: process.env.FRONT_DOMAIN, // 도메인 추가
        })
        .cookie(TOKEN.REFRESH_TOKEN, `Bearer ${refreshToken}`, {
          maxAge: MAX_SIZE.REFRESH_TOKEN_MAX_AGE,
          httpOnly: true,
          sameSite: 'none',
          secure: true,
          domain: process.env.FRONT_DOMAIN,
        })
        .redirect(HttpStatus.MOVED_PERMANENTLY, process.env.FRONT_URL)
    );
  }

결과

프론트 도메인을 입히려는 수 많은 시도를 했지만

현재 host URL과 관련한 domain 속성이 잘못됐다라는 오류 메시지와 함께 쿠키가 아예 전송이 되지 않았습니다.

3. 역시 답은...

애초에 문제는 Origin이 다르기 때문입니다.

도메인만 맞춰준다면 여러 cross-origin 문제에 대해 해결하기 위한 걱정을 덜 해도 되며

같은 도메인이라면 스코프도 같기 때문에 걱정될 것이 없을 것 같습니다

결과

아주 아주 잘 됩니다.

cross-origin은 보안때문에 제약이 많으니 처음부터 same-origin을 고려하는게 맞는 것같습니다!

애초에 redirect 문제가 아니라 cross-origin이 문제였던 것 같습니다.

결론

쿠키 스코프가 서버로 잡히는 것은 redirect 문제가 아닌 cross-origin 문제!

서버 배포할 때는 클라이언트와 같은 origin으로 지정해주는 것이 보안적으로도, 개발적으로도 좋다!

대조군(초기)실험1(영속적인 redirect)실험2(쿠키 스코프 명시)실험3(same-domain)
HTTPSOOOO
Access-Control-Allow-CredentialsOOOO
Access-Control-Allow-OriginsFE SERVERFE SERVERFE SERVERFE SERVER
영속적인 redirectXOXX
쿠키 Domain 명시XXOX
same-domainXXXO
FE 쿠키 접근 결과XXXO
profile
창업에 관심 많은 개발자입니다

0개의 댓글