목차
- SessionId를 쿠키로 전달하는데 쿠키가 오지를 않는다...?
- Browser의 쿠키
- Application Server > Next Web Server > Browser 로의 쿠키 전달 과정
- Next.js SSR 요청 시 쿠키의 흐름
- 쿠키가 오가기 위한 조건
- 해결
1. SessionId를 쿠키로 전달하는데 쿠키가 오지를 않는다...?
1.1. 문제 상황
저번 포스팅에서 client-server 간의 인증은 Session 방식을, server-spotify server 간의 인증은 JWT를 사용하는 시스템을 설계했다.
이를 신나게 구현하고 있는 도중 문제가 발생했다. Express 서버에서 Next.js 클라이언트로 sessionId를 쿠키에 담아 전달했다. 그런데 브라우저의 Application > Cookie 섹션에서 sessionId를 찾을 수 없었다. 분명히 서버에서 쿠키를 설정했는데 왜 보이지 않는 걸까?
1.2. 원인 파악: Next.js의 구조
문제의 핵심은 Next.js의 구조에 있다. Next.js는 server side와 client side 렌더링을 모두 지원하는데, 따라서 쿠키가 어디서 설정되고 어떻게 전달되는지 정확히 이해하는 것이 중요하다. 이 문제를 해결하기 위해서 쿠키가 Express 서버에서 출발해서 Next.js 서버를 거쳐 최종적으로 브라우저에 도착하는 전체 과정을 자세히 살펴볼 필요가 있다. 각 단계에서 쿠키가 어떻게 처리되고 전달되는지 이해하면, 왜 쿠키가 안보였는지, 그리고 어떻게 이 문제를 해결할 수 있는지 명확해질 것이다.
2. 브라우저의 쿠키
2.1. 브라우저가 쿠키를 저장하는 과정
① 서버로부터의 Set-Cookie 헤더 수신
서버가 HTTP 응답에 Set-Cookie 헤더를 포함하면, 브라우저는 이를 처리하여 쿠키를 저장소에 기록한다.
HTTP/1.1 200 OK
Set-Cookie: sessionId=abc123; Path=/; Domain=example.com; Secure; HttpOnly; SameSite=Lax
② 브라우저의 쿠키 저장소:
브라우저는 수신한 쿠키를 내부 저장소(ex. Chrome의 경우 SQLite 데이터베이스)에 저장한다. 이 DB는 다음과 같은 필드로 구성된다.
host_key: 쿠키의 도메인
name: 쿠키 이름
value: 쿠키 값
path: 쿠키가 유효한 경로
secure: HTTPS 요청에서만 전송 여부
expires: 쿠키의 만료 시간
기타 플래그(HttpOnly, SameSite 등)
Chrome의 Cookies 파일 구조(SQLite)를 보면 다음과 같다.
host_key | name | value | path | secure | expires_utc | is_httponly | samesite |
example.com | sessionId | abc123 | / | 1 | 1893456000 | 1 | 1 |
이를 직접 확인하려면, SQLite 뷰어를 설치하고 Chrome의 Cookies 파일 경로를 찾아서 열면 된다. 쿠키 데이터는 cookies 테이블에 저장되며, 여기서 name, value, host_key 등을 확인할 수 있다.
C:\Users\<사용자 이름>\AppData\Local\Google\Chrome\User Data\Default\Cookies
2.2. 개발자 도구 Application > Cookie 섹션
개발자 도구의 Application 탭의 Cookie 섹션은 브라우저가 SQLite DB에 저장된 쿠키 데이터를 읽어와 가공해서 화면에 표시하는 곳이다. 여기서 쿠키를 삭제하거나 수정하면 브라우저가 SQLite DB 파일을 업데이트하게 된다.
2.3. HTTP 요청 시 쿠키 자동 포함
브라우저는 쿠키 관리의 주체로서, HTTP 요청을 생성할 때 자동으로 관련 쿠키를 포함한다. HTTP protocol에 내장된 동작이며, client가 명시적으로 쿠키를 설정하지 않아도 브라우저가 자동으로 처리하는 부분이다.
브라우저가 URL에 대한 HTTP 요청을 생성할 때, 내부 쿠키 저장소를 참조하여 해당 요청에 포함할 쿠키를 결정한다.
① URL과 쿠키 조건 비교: 브라우저는 요청 URL의 도메인, 경로, 스키마(HTTP/HTTPS) 등을 쿠키 저장소에 저장된 조건과 비교한다.
② 조건을 만족하는 쿠키를 찾아 요청의 Cookie 헤더에 추가한다.
🤔 직접 추가할 수는 없을까?
클라이언트(JavaScript)는 요청의 HTTP 헤더에 Cookie 값을 임의로 추가할 수는 없다. 보안상의 이유로 브라우저는 JavaScript에서 Cookie 헤더를 직접 조작하는 것을 금지하기 때문이다. 다만, JavaScript는 쿠키를 읽거나(document.cookie), 브라우저에 새로운 쿠키를 저장하도록 요청할 수 있다.(document.cookie = "sessionId=abc123; path=/";)
// client code fetch('https://example.com/profile', { method: 'GET', headers: { 'Cookie': 'session_id=abc123' // ❌ 금지된 동작 } });
3. Application Server > Next Web Server > Browser 로의 쿠키 전달 과정
로그인할 때 서버에서 클라이언트로 sessionId가 전달되는 과정에 대해 알아보자.
① Application Server가 sessionId를 생성하고, 이를 쿠키에 포함해서 Next Web Server로 전송한다.
// Express
res.cookie(name, value, options);
② Next Web Server는 sessionId를 받아 쿠키를 설정한다.
// Next.js server action
import { cookies } from 'next/headers'
...
const cookieStore = await cookies()
cookieStore.set({
name: 'sessionId',
value: sessionId || '',
httpOnly: true,
path: '/',
})
Next Web Server가 sessionId를 클라이언트로 전달할 때 Set-Cookie 헤더를 사용하는데, cookieStore.set() 메서드를 통해 자동으로 처리된다.
③ 브라우저는 서버로부터 응답을 받을 때, Set-Cookie 헤더를 통해 쿠키를 자동으로 저장한다.
이후 같은 도메인으로의 요청에 대해 자동으로 쿠키를 포함시킨다.
4. Next.js SSR 요청 시 쿠키의 흐름
① 클라이언트가 HTTP 요청 생성:
- 사용자가 브라우저에서 URL을 입력하거나 링크를 클릭하면, 브라우저는 해당 URL에 대한 HTTP 요청을 생성한다.
- 이 과정에서 브라우저는 쿠키 저장소를 참조하여 쿠키를 Cookie 헤더에 포함한다.
② Next Web Server가 쿠키 수신:
- 브라우저의 HTTP 요청은 Next.js 서버로 전달되고, 쿠키 정보는 요청 헤더에 포함된다.
GET /page HTTP/1.1
Host: example.com
Cookie: sessionId=abc123; theme=dark
③ SSR 과정 중 API 서버 요청:
- Next.js Web Server는 SSR 중 데이터가 필요하면 Application Server로 HTTP 요청을 보낸다.
- 이 요청에 브라우저로부터 받은 쿠키를 포함하려면, 서버가 수신한 Cookie 헤더 값을 API 요청에 명시적으로 추가해야 한다.
// Next Web Server code
fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Cookie': 'sessionId=abc123; theme=dark',
},
});
5. 쿠키가 오가기 위한 조건
Client-Server 간 쿠키가 오가기 위해서 설정해주어야 하는 것들에 대해 알아보자.
5.1. Node.js에서 cors 설정
app.use(
cors<cors.CorsRequest>({
origin: ["http://localhost:3000", "https://localhost:3000"],
credentials: true,
})
);
5.2. Cookie Options
const cookieOptions: {
httpOnly: boolean;
maxAge: number;
sameSite: "lax" | "strict" | "none";
secure: boolean;
path: string;
} = {
httpOnly: true,
maxAge: 60 * 60 * 1000,
sameSite: "none",
secure: true,
path: "/",
};
httpOnly
httpOnly를 true로 설정하면 js에서 쿠키에 접근할 수 없다. 즉, document.cookie로 접근할 수 없으므로 XXS 공격으로부터 쿠키가 탈취되는 것을 방지할 수 있다.
maxAge
쿠키의 유효기간을 밀리초 단위로 설정한다. 60 * 60 * 1000으로 설정하면 쿠키가 생성된 시점부터 1시간 후에 자동으로 삭제된다.
sameSite
쿠키가 다른 사이트에서 요청될 때도 전송될지 여부를 결정한다.
- "lax": 쿠키는 기본적으로 같은 사이트에서만 전송되는데, 사용자가 링크를 클릭해서 다른 사이트로 이동한 경우(탐색 요청)는 전송된다.
- "strict": 오직 같은 사이트에서만 전송되며, 외부 사이트에서의 모든 요청에서는 쿠키가 전송되지 않는다.
- "none": 모든 cross 사이트 요청에 쿠키가 항상 전송된다. 이 경우, secure: true가 필수이다.
secure
쿠키를 HTTPS 프로토콜을 사용하는 경우에만 전송하도록 제한한다. HTTP이면 전송되지 않는다. 보안 강화를 위해 sameSite: "none"과 함께 사용한다.
path
쿠키가 서버의 어떤 경로에서 유효한지 설정한다. default 값은 쿠키가 설정된 경로이지만, path: '/'로 설정하면 서버 전체 경로에서 유효하다. 즉, 클라이언트가 서버의 어떤 경로에 요청을 하더라도 쿠키가 전송되며, 이를 통해 인증 상태를 서버의 모든 엔드포인트에서 공유할 수 있다.
5.3. Client에서 fetch 요청 시 credentials: 'include' 설정
fetch API는 기본적으로 쿠키를 포함하지 않는다. 따라서 쿠키를 전송하려면 credentials 옵션을 설정해야 한다.
credentials 옵션에는 다음 세 가지가 있다.
same-origin(default):
동일 출처인 경우에만 쿠키를 전송한다. (protocol, host, port가 모두 같은 경우)
include:
동일 출처뿐 아니라 cross domain 요청에서도 쿠키를 포함한다. 이때, 서버 응답의 쿠키가 Access-Control-Allow-Credentials: true를 설정한 경우에만 클라이언트가 저장할 수 있다.
omit:
요청에 쿠키나 인증 정보를 포함하지 않는다.
정리해보면, 쿠키가 전송되는 조건은 다음과 같다.
1. 요청 헤더에 쿠키를 포함할 수 있도록 credentials: 'include'를 설정
2. 서버가 응답 시 Access-Control-Allow-Credentials: true를 설정
3. 쿠키의 sameSite 속성이 none
6. 해결
문제 상황을 다시 보자. Express 서버에서 보낸 sessionId가 Application > Cookie 섹션에서 보이지 않았고, fetch 요청을 보내도 Express 서버까지 sessionId가 전달되지 않았던 이유는 다음과 같다.
1. Next Web Server에서 브라우저로 전달할 쿠키를 설정하지 않았다.
2. Express server로 fetch 요청을 보낼 때, 쿠키를 명시적으로 포함시키지 않았다.
이 경험을 통해 서버와 클라이언트 간의 데이터 흐름을 정확하게 이해하고 구현하는 것이 중요하다는 것을 다시 한번 깨달았다. 서버 간 통신과 서버-클라이언트 간 통신의 차이를 명확히 인식하고, Next.js의 구조를 더 공부해야겠다.
'Projects,Activity > Spotify' 카테고리의 다른 글
GraphQL 적응기 - RESTful 마인드셋에서 벗어나기 위한 몸부림 (4) | 2024.12.07 |
---|---|
Optimization(1): Throttle & Debounce (4) | 2024.12.05 |
Apollo client의 캐시가 Next.js를 만나면: SSR, RSC + Next.js 15의 바뀐점 (2) | 2024.12.04 |
Spotify API와 자체 DB를 통합한 인증 시스템 - JWT+JWT vs Session+JWT (0) | 2024.11.06 |