Chaerrot🥕

모바일 브라우저에서 쿠키가 저장되지 않는 문제 해결하기

2025-01-15
20
59

들어가며

작년 8월 경 해당 프로젝트를 배포할 때, 기간 내에 끝내야 했기 때문에 깊게 공부하지 못하고 일단 돌아가게끔 해둔 부분이 꽤 있었다.

이후 작년 11월 경 갑자기 클라이언트와 서버의 통신이 끊겼다.😂 프론트와 백 모두 배포 라인을 건든 적이 없는데, 갑자기 뻗어버려서 문제를 해결하기 위해서 배포 과정을 처음부터 끝까지 되돌아보며 정리했다.

이 글은 배포 과정에서 모바일 브라우저에서 쿠키가 저장되지 않았던 문제를 해결한 방법을 백엔드 팀원분과 함께 정리한 글이다.

문제 상황

프론트엔드(Next.js 앱을 Vercel로 배포), 백엔드(Spring 앱을 AWS로 배포) 모두 배포를 완료한 상태였으며, 배포 도메인은 다음과 같았다.

  • 프론트엔드: boardbuddyapp.vercel.app
  • 백엔드: boardbuddyapp.com

PC 웹 브라우저로는 모든 통신이 정상적으로 이루어지고 있었으나, 모바일 웹 브라우저에서는 로그인하고 다음 화면으로 넘어간 이후부터 모든 요청에 대해 401 에러가 발생했다.

더 정확히는, 모바일 운영체제에 따라 다음과 같았다.

  • 안드로이드 : 정상 동작
  • iOS : 401 에러 발생

백엔드 서버에서는 세션 방식으로 사용자 인증을 관리하고 있었다. 그렇기 때문에 클라이언트는 서버로부터 받은 session_id 값을 쿠키에 저장해야 하는데, iOS에서는 쿠키가 제대로 저장되지 않았기 때문에 위와 같은 문제가 발생한 것이었다.

모바일 웹 브라우저에서 쿠키 저장이 안되는 이유

왜 iOS에서는 쿠키가 제대로 저장되지 않았을까?

사파리의 크로스 사이트 추적 방지 옵션

사파리에는 ‘크로스 사이트 추적 방지’ 설정이 존재한다. 기본값은 on으로, 이 설정은 타사 쿠키 및 데이터를 기본적으로 차단한다. 따라서 크로스 사이트 추적 방지 옵션을 끄면, 쿠키가 제대로 저장되어 정상적으로 서버와 통신할 수 있었다.

그러나 이 방법은 임시로 문제를 해결하는 방법일 뿐이며, 보안 상 해당 옵션을 켜두는 것이 안전하기 때문에 근본적인 원인을 찾는 것이 좋아보였다.
또한, 사파리는 이렇게 했을 경우 문제가 해결되었지만 크롬의 경우에는 유사한 설정을 꺼도 서버와 통신이 되지 않았다.

그렇게 좀 더 구글링해보니, 쿠키의 samesite 속성과 관련된 글이 많이 보이기 시작했다.

쿠키의 SameSite 속성

먼저, 서버 측에서는 세션 쿠키와 관련된 설정을 httpOnly, secure, same-site: none로 적용한 상태였다.

  • httpOnly: JavaScript를 통해 쿠키에 접근할 수 없게 되어, 악성 스크립트를 통해 쿠키 값에 접근하는 것을 막는다.
  • secure: HTTPS를 사용하는 통신에서만 쿠키를 전송한다.
  • same-site: 쿠키가 퍼스트 파티 또는 동일 사이트 컨텍스트로 제한되는지 여부를 설정할 수 있다. 3가지 정책(None, Strict, Lax) 중 하나를 선택하여 설정한다.
    • None: 크로스 사이트 요청의 경우에도 쿠키가 항상 전송된다. SameSite가 탄생하기 전의 쿠키 동작 방식과 같다.
    • Strict: 가장 보수적인 정책으로, 크로스 사이트 요청의 경우 쿠키가 전송되지 않는다.
    • Lax: Strict에 비해 느슨한 정책으로, 크로스 사이트 요청의 경우 서드 파티 쿠키는 대체로 전송되지 않지만 몇 가지 예외적인 요청에는 전송된다.

site의 의미
프로토콜(scheme) + registerable 도메인을 말한다.
ex) https://www.web.dev의 site는 https + web.dev이다.
ex) 사용자가 https://www.web.dev에 있고 https://static.web.dev로 이미지를 요청하는 경우 동일 사이트 요청이다.

이때 문제는 SameSite이다. 아직까지 SameSite=None을 인식하지 못하는 클라이언트는 이를 무시하고 특성이 설정되지 않은 것처럼 계속 작동하게 된다고 한다.
즉, 버전이 낮은 브라우저에서는 SameSite=None을 지원하지 않으므로 SameSite=Lax인 것처럼 작동한다는 것이다.

쿠키 설정 속성과 브라우저의 호환성은 여기에서 확인할 수 있다.

그래서 우리 팀은 site를 통일해서 이 문제를 해결하기로 했다.

Site의 개념

위에서 잠깐 언급하긴 했지만, SameSite 속성을 이해하는 데 site와 origin의 개념이 상당히 헷갈렸었다.
그래서 처음에는 이 문제를 해결하기 위해 출처가 같아야 하니, 도메인을 통일해야 한다고 생각했다. (이미 프로토콜은 https로 같은 상태였으므로)

지금 와서 생각하면 왜 헷갈렸나 싶지만, 출처, 사이트, 도메인이라는 용어까지 혼용해서 사용하다 보니 백엔드 팀원분과 소통할 때도 어질어질했다.

그러나 Same-Site와 Same-Origin의 컨텍스트는 완전히 다르다.

결론적으로는 아래 그림대로만 이해하면 된다.
domain, eTLD, site, origin (출처: https://www.michalspacek.com/origin-site-etld-etld-plus-one-public-suffix-psl-what-are-they)domain, eTLD, site, origin (출처: https://www.michalspacek.com/origin-site-etld-etld-plus-one-public-suffix-psl-what-are-they)

브라우저는 site(scheme + registrable/eTLD + 1)이 같으면 동일 사이트(same-site), 다르면 크로스 사이트(cross-site) 로 간주한다.

이때 중요한 것은, Origin이 같으면 자연스레 Site가 같아지기 때문에 same-site라고 판단하는 것이지,
무조건 Origin이 같아야 Site가 같은 것은 아니라는 점이다.
ex) https://www.web.dev:443https://foo.web.dev:443는 Origin은 다르지만 Site는 동일하다.

서브도메인 설정으로 site 통일하기

CNAME 레코드와 SameSite, secure, http-only 속성 및 Vercel 커스텀 도메인 설정

  1. Route53 에서 CNAME 레코드 생성
    레코드 생성레코드 생성
  • AWS Route 53에서 CNAME 레코드를 활용해 m.boardbuddyapp.com을 프론트엔드 Vercel 서버 도메인으로 매핑
  • CNAME 설정을 통해서 아래와 같이 동작하게 된다
    • 모바일 브라우저는 m.boardbuddyapp.com에서 요청이 발생했음을 인식
    • m.boardbuddyapp.com은 백엔드 도메인 boardbuddyapp.com과 site가 동잃함
    • 결과적으로 Cross-Site 요청이 아닌 Same-Site 요청으로 처리
  1. 기존에 작성되어있던 YML 파일에서의 SameSite=None, secure, http-only 속성 삭제

  2. nginx 파일 내에 SameSite=Lax, secure, http-only 속성 설정하기

    nginx.conf
    proxy_cookie_path / "/; Secure; HttpOnly; SameSite=Lax";
  3. Vercel 프로젝트 설정에서 도메인 추가하기
    프로젝트 설정에서 도메인 추가프로젝트 설정에서 도메인 추가

✔️ yml 파일이 아닌 nginx 파일에 다시 작성한 이유

현재 프로젝트에서는 인증 관련 세션 쿠키만 필요하다. 기존에는 yml 파일 내에 쿠키 관련 속성을 작성 했었으나, yml 파일에서 쿠키 관련 설정을 하게되면 spring boot 앱에서만 생성되는 쿠키에 대해서만 속성이 설정된다.

따라서 외부에서 얘기치 못한 쿠키가 전송되는 경우를 생각하여 프로젝트 전체에 동일한 쿠키 관련 설정을 적용하기 위해 nginx 파일에 쿠키 관련 속성을 작성했다.

만약에 동적으로 어떠한 요청에 대해서 별도의 쿠키 보안 설정이 필요한 경우에서는 yml 파일에서 쿠키 관련 설정을 작성하고 기존의 nginx 파일의 쿠키 관련 설정을 최소화 하는 방식으로 리팩토링이 필요할 수도 있다.

왜 문제가 해결된것일까?

✔️ Cross-Site와 Same-Site의 차이

  • 기존 구조
    • 프론트엔드: https://boardbuddyapp.vercel.app
    • 백엔드: https://boardbuddyapp.com
    • 브라우저는 이를 Cross-Site 요청으로 간주하여 쿠키를 차단
  • 변경된 구조
    • 프론트엔드: https://m.boardbuddyapp.com
    • 백엔드: https://boardbuddyapp.com
    • 브라우저는 이를 Same-Site 요청으로 간주한다. (site: boardbuddyapp.com)

✔️ 쿠키의 Domain 속성

쿠키의 Domain 속성이 boardbuddyapp.com으로 설정되어 있었다면, 모든 서브도메인에서도 해당 쿠키가 유효하다. 이를 통해 m.boardbuddyapp.com에서 쿠키를 정상적으로 사용할 수 있게 되었다.

Cross-Site 요청 해결 메커니즘

Cross-Site 요청이란, 브라우저가 site가 다른 두 도메인 간의 요청을 처리하는 상황을 말한다.
그렇다면 기존에 이슈가 발생했던 구조를 알아보자.

이슈가 발생했던 구조

  • 프론트엔드 도메인: https://boardbuddyapp.vercel.app
  • 백엔드 도메인: https://boardbuddyapp.com

브라우저는 두 도메인의 site가 다르기 때문에 요청을 Cross-Site 요청으로 간주했었다.

✔️ SameSite 정책의 영향
SameSite 쿠키 정책은 Cross-Site 요청에서 쿠키의 전송을 제한한다.
그렇다면, 브라우저가 쿠키를 전송하지 않는 이유는 2가지를 생각할 수 있다.

  1. Cross-Site로 간주됨
    브라우저는 https://boardbuddyapp.vercel.app에서 발생한 요청이 백엔드 도메인(https://boardbuddyapp.com)으로의 Cross-Site 요청임을 인식
  2. SameSite 쿠키 정책에 의해 차단
    기존에 이미 SameSite=None 으로 설정하고 PC환경에서 테스트 했으므로 해당 없음

문제를 해결하기 위해 프론트엔드 도메인을 m.boardbuddyapp.com으로 변경했다. 이로 인해 브라우저가 요청을 Same-Site 요청으로 간주하게 되었다.


해결 과정 요약

기존 구조변경된 구조
프론트엔드 도메인https://boardbuddyapp.vercel.apphttps://m.boardbuddyapp.com
백엔드 도메인https://boardbuddyapp.comhttps://boardbuddyapp.com
브라우저의 요청 인식Cross-Site 요청으로 간주Same-Site 요청으로 간주
SameSite 정책 적용쿠키 전송 차단쿠키 전송 허용

결과

  • 모바일 환경에서도 쿠키가 정상적으로 전송됨
    • Cross-Site가 아닌 Same-Site로 인식되면서 모바일 브라우저에서 쿠키가 차단되지 않음
  • 사용자 경험 개선
    • 모바일 환경에서도 사용자의 인증이 유지되어 접근성과 사용성 향상

교훈 및 추가 고려사항

  • 새로운 지식
    모바일 브라우저는 PC 브라우저보다 보안 정책이 엄격하므로 이를 염두에 두고 개발해야 한다는것을 알 수 있었다.
  • SameSite 정책 이해
    SameSite 정책은 브라우저의 보안 정책을 강화하기 위한 중요한 요소이므로, 모바일 환경에서는 정책이 더 엄격하게 적용되므로 이를 고려한 설계가 필요하다.
  • CNAME 레코드 활용
    프론트엔드와 백엔드를 같은 도메인 체계 내에서 동작하도록 구성하면 Cross-Site 관련 문제를 효과적으로 해결할 수 있다는 사실을 알게 되었다.

결론

이번 이슈 해결은 도메인 구조와 브라우저 보안 정책의 상호작용이 웹 서비스 동작에 얼마나 큰 영향을 미치는지 알 수 있었다.

  1. 문제를 해결하기 위한 핵심은 브라우저 정책의 정확한 이해
    모바일 환경에서 발생한 쿠키 전송 문제는 SameSite 쿠키 정책과 Cross-Site 요청 처리 방식을 제대로 이해하고 이를 바탕으로 적용함으로써 해결할 수 있었으며, 브라우저가 요청을 Same-Site 요청으로 처리하도록 도메인 구조를 재설계한 것이 중요했다.
  2. 브라우저의 기술적 변화에 대한 적응
    브라우저는 사용자 데이터를 보호하기 위해 SameSite 쿠키 정책과 HTTPS 의무화를 강화시키는 추세인것 같다. 이러한 변화를 단순히 문제를 발생시키는 원인으로 생각하지 않고, 애플리케이션의 보안 수준을 높일 수 있는 기회로 보면 좋을것 같다.
  3. 적용된 접근 방식
    AWS Route 53을 활용한 CNAME 설정과 도메인 매핑은 간단하면서도 이슈를 해결할 수 있는 중요한 방법이였으며, 브라우저가 어떠한 요청을 ‘동일한 사이트의 안전한 환경’으로 인식하도록 도메인을 재 설계한 점은 모바일 환경에서 안정적인 사용자 경험을 제공하는 데 중요한 역할을 했다.

요약하자면, 브라우저의 보안 정책 변화가 단순히 제약하는것만이 아니라 더 좋은 방향의 애플리케이션 설계를 가능하게 해주는 측면도 있는것 같다.

따라서, 브라우저의 보안 정책을 공부하여 이해하고, 이를 기반으로 도메인을 설계하면 모바일 환경에서도 일관되고 안전한 서비스를 제공할 수 있을것이다.

레퍼런스

SameSite cookies explained
모바일 브라우저에서는 로그인이 안 되는 이유
https://support.apple.com/ko-kr/guide/ipho
SOP(Same Origin Policy)의 한계와 쿠키(Cookie)의 SameSite 속성의 활용
"Same-site" and "same-origin"
Origin, site, eTLD, eTLD+1, public suffix, PSL. What are they?

+) iOS 정책에 따른 쿠키 관리 방법의 차이?

결론적으로 site를 통일해서 쿠키 문제는 해결이 되었으나, 궁금한 부분이 있어 추가로 남겨둔다.

이 문제가 발생했을 때는 위에서 언급했듯이 2024년 8월 경이었고, 호환성 표를 보면 이미 2019년 정도부터 SameSite=None 속성을 모든 브라우저가 지원하고 있는 것을 알 수 있다.

그렇다면 iOS에서 Safari는 크로스 사이트 추적 방지 옵션을 끄면 쿠키가 저장이 되었는데, 왜 크롬은 쿠키가 저장되지 않았던 걸까?🤔

챗지피티에게 물어본 결과, iOS에서 Safari와 Chrome의 쿠키 저장에서 차이가 발생하는 주된 이유는 iOS 정책 때문이라고 한다.

Safari는 Apple의 기본 브라우저로, iOS와 완전한 통합이 되어있어 쿠키 저장에 제한이 없다. 그러나 Chrome을 포함한 서드파티 브라우저는 iOS에서 몇 가지 제약사항이 있다.

  1. iOS의 정책상 모든 서드파티 브라우저는 Safari의 Webkit 엔진을 사용해야 한다.
  2. 쿠키와 웹 저장소에 대한 접근이 일부 제한될 수 있다.
  3. Intelligent Tracking Prevention(ITP) 정책의 영향을 받을 수 있다.
  4. ...

Chrome은 안드로이드에서는 Blink 엔진, iOS에서는 Webkit 엔진 위에서 동작하는데, iOS에서 서드파티 브라우저에 몇 가지 제약을 걸고 있다. 따라서 이것 때문에 같은 Chrome이라도 iOS와 안드로이드에서 동작이나 성능이 다를 수 있다고 한다.

이 내용은 아직 신뢰성있는 출처를 찾지 못해서 확실하다고는 말할 수 없다. 그래도 '오 이런 이유일수도..?' 싶었다.
추후에 관련해서 자료를 좀 더 찾아보게 되면 추가하도록 하겠다.