서버가 사용자의 웹 브라우저에 전송하는 작은 데이터 조각
Set-Cookie
header 사용Set-Cookie: <cookie-name>=<cookie-value>
Cookie
header 사용Cookie: name=value; name2=value2; name3=value3
Expires
: 날짜 타임스탬프로 지정할 수 있는 쿠키의 최대 생명주기
Set-Cookie: id=1a2b3c4d; Expires=Thu, 31 Oct 2021 07:28:00 GMT;
Max-Age
: 쿠키가 만료할 때까지의 시간(초)
Set-Cookie: id=1a2b3c4d; Max-Age=2592000
- 세션 쿠키(Session cookies)
- Expires, Max-Age 속성이 없는 쿠키로 현재 세션이 끝날 때 삭제된다.
- 브라우저는 "현재 세션"이 끝나는 시점을 정의하며, 어떤 브라우저들은 재시작할 때 세션을 복원해 세션 쿠키가 무기한 존재할 수 있도록 한다.
- 영속 쿠키(Permanent cookies)
- Expires 속성에 명시된 날짜에 삭제되거나, Max-Age 속성에 명시된 기간 이후에 삭제된다.
- 쿠키가 저장되는 "클라이언트"의 시간 기준
: Cross-Site Scripting (XSS) 공격 방지하기 위해 JavaScript의 Document.cookie
API에 접근할 수 없도록 하는 속성
: "HTTPS 프로토콜"을 사용한 요청일 때만 쿠키가 전송되도록 하는 속성
: 쿠키가 전송되게 할 host(server domain) 명시
: Site 간 요청과 함께 쿠키가 전송될지를 제어하여 Cross-Site Request Forgery (CSRF) 공격 방지하기 위한 방법을 제공하는 속성
chromium의 설명을 참고하면 다음과 같다.
The SameSite attribute
The SameSite attribute of a cookie specifies whether the cookie should be restricted to a first-party or same-site context. Several values of SameSite are allowed:
- A cookie with "SameSite=Strict" will only be sent with a same-site request.
- A cookie with "SameSite=Lax" will be sent with a same-site request, or a cross-site top-level navigation with a "safe" HTTP method.
- A cookie with "SameSite=None" will be sent with both same-site and cross-site requests.
Legacy SameSite behavior
As of Chrome 80 (see launch timeline), a cookie that does not explicitly specify a SameSite attribute will be treated as if it were "
SameSite=Lax
". In addition, any cookie that specifies "SameSite=None
" must also have the Secure attribute. (See https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.5 for the definition of the Secure attribute.)
SameSite
속성을 별도로 설정하지 않으면 default로 Lax
이다.Lax
는 same-site 요청일 때에는 쿠키가 전송되고, 이 외에는 Top Level Navigation(웹 페이지 이동)과, "안전한" HTTP 메서드 요청(GET, HEAD, OPTIONS, TRACE와 같이 상태를 변경하지 않는 요청)의 경우 전송된다.SameSite
속성을 None
으로 설정해야 하며 이 경우 https 통신을 할 때만 쿠키를 전송하도록 하는 Secure
속성이 반드시 true
여야 한다.https://developer.mozilla.org/en-US/docs/
https://support.mozilla.org/en-US/
https://developer.mozilla.org/en-US/docs/
https://example.com
http://example.com
https://example.com
https://example.com:8080
https://example.com
- same-site: 동일한 등록 가능한 도메인을 공유하며, Scheme(protocol)이 동일한 경우.
- cross-site: 다른 등록 가능한 도메인을 가지거나, Scheme(protocol)이 다를 경우.
Sec-Fetch-Site
를 확인하면 다음 4가지 값 중 하나를 가진다.Sec-Fetch-Site: cross-site
Sec-Fetch-Site: same-origin
Sec-Fetch-Site: same-site
Sec-Fetch-Site: none
Origin
, Referer
등의 유용한 정보가 담겨 있다.Auth API를 구현할 때 Cookie를 사용했다. 👉 API 별 flow 참고
댕댕워크 프로젝트를 진행하면서 프로그래머스 데브 코스 수료 이후 AWS 지원이 종료되면서 서버를 이전해야 할 일이 생겼다. 서버 이전을 하기 전 환경은 다음과 같았다.
environment | client(frontend) | server(backend) |
---|---|---|
local | http://localhost:3000 | http://localhost:3333 |
prod | https://dangdang-walk.vercel.app | https://dangdang-walk.prgms-fullcycle.com |
배포 환경이 cross-site였기 때문에 쿠키 속성은 다음과 같이 SameSite=None
, Secure
를 사용했었다.
private readonly cookieOptions: CookieOptions = {
httpOnly: true,
sameSite: this.isProduction ? 'none' : 'lax',
secure: this.isProduction,
⁝
};
브라우저에서 크로스 사이트 추적 방지 동작 때문에 서드 파티 쿠키가 전송되지 않는 문제가 있었기 때문에 서버를 이전하면서 아예 same-site로 변경하기로 했고 클라이언트와 서버의 도메인을 맞춰주기 위해 porkbun에서 도메인(https://dangdang-walk.xyz)을 구매했다.
(공개 접미사 목록에 xyz
가 존재하는 것을 확인할 수 있다.)
Same site로 만들기 위해서는 다음과 같이 두 가지 방법이 있었고 회의를 거친 결과 간단히 subdomain을 사용하기로 결정했다.
subdomain 사용✅
https://dangdang-walk.xyz
https://api.dangdang-walk.xyz
nginx를 이용해 port forwarding
https://dangdang-walk.xyz/3000
https://dangdang-walk.xyz/3333
그래서 서버 이전 후 환경은 다음과 같이 same-site로 변경되었다.
environment | client(frontend) | server(backend) |
---|---|---|
local | http://localhost:3000 | http://localhost:3333 |
prod | https://dangdang-walk.xyz | https://api.dangdang-walk.xyz |
Sec-Fetch-Site
request header를 보면 same-site
인 것을 확인할 수 있다.
Cookie 설정은 Interceptor를 통해 처리해주었다. (코드 참고)
@Injectable()
export class CookieInterceptor implements NestInterceptor {
constructor(
private configService: ConfigService,
private logger: WinstonLoggerService,
) {}
private readonly isProduction = this.configService.get<string>('NODE_ENV') === 'prod';
private readonly sessionCookieOptions: CookieOptions = {
httpOnly: true,
secure: this.isProduction,
sameSite: 'lax',
};
private readonly refreshCookieOptions: CookieOptions = {
...this.sessionCookieOptions,
maxAge: TokenService.TOKEN_LIFETIME_MAP.refresh.maxAge,
};
⁝
HttpOnly
속성을 사용하고, Secure
속성은 배포 환경에서만 사용한다.SameSite
속성은 기본 값인 Lax
를 사용한다.maxAge
를 지정해주었다.