155 lines
4.4 KiB
Markdown
155 lines
4.4 KiB
Markdown
# Multi-Domain JWT 토큰 공유 분석
|
|
|
|
## 현재 구조 진단
|
|
|
|
### 도메인 구성
|
|
| 앱 | 도메인 | 포트(로컬) |
|
|
|----|--------|-----------|
|
|
| cs | `cs.onasys.co.kr` | 3000 |
|
|
| as | `as.onasys.co.kr` | 3003 |
|
|
| backend | 공통 1개 | 8000 |
|
|
|
|
### 핵심 문제
|
|
|
|
브라우저 쿠키는 **Same-Origin Policy** 적용: `cs.onasys.co.kr`에서 설정한 쿠키는 `as.onasys.co.kr`에서 **읽을 수 없음**.
|
|
|
|
현재 두 앱 모두 `NEXT_PUBLIC_COOKIE_DOMAIN` 환경변수가 **설정되지 않음** → 쿠키 `domain` 속성 없음 → 각 앱이 자기 도메인에만 쿠키 저장.
|
|
|
|
### 코드 흐름 분석
|
|
|
|
```
|
|
authConfig.cookieDomain = process.env.NEXT_PUBLIC_COOKIE_DOMAIN (현재 undefined)
|
|
↓
|
|
token-manager.ts: getCookieDomain() → undefined 반환
|
|
↓
|
|
setCookie(key, value, { domain: undefined, ... }) ← domain 없이 저장
|
|
↓
|
|
결과: cs.onasys.co.kr 쿠키 ≠ as.onasys.co.kr 쿠키 (공유 불가)
|
|
```
|
|
|
|
---
|
|
|
|
## 해결 방법: Parent Domain Cookie
|
|
|
|
### 원리
|
|
쿠키 `Domain` 속성을 **부모 도메인**으로 설정하면 모든 서브도메인이 공유:
|
|
|
|
```
|
|
Domain=.onasys.co.kr
|
|
→ cs.onasys.co.kr ✅ 읽기/쓰기 가능
|
|
→ as.onasys.co.kr ✅ 읽기/쓰기 가능
|
|
```
|
|
|
|
---
|
|
|
|
## 구현 방법
|
|
|
|
### Step 1: 환경변수 추가
|
|
|
|
**apps/cs/.env.production** (또는 `.env.local` 배포용)
|
|
```env
|
|
NEXT_PUBLIC_COOKIE_DOMAIN=.onasys.co.kr
|
|
```
|
|
|
|
**apps/as/.env.production**
|
|
```env
|
|
NEXT_PUBLIC_COOKIE_DOMAIN=.onasys.co.kr
|
|
```
|
|
|
|
> [!IMPORTANT]
|
|
> 반드시 앞에 `.` (점)을 붙여야 서브도메인 전체에 적용됩니다.
|
|
|
|
### Step 2: 코드 변경 불필요
|
|
|
|
`token-manager.ts`의 `getCookieDomain()`이 이미 `config.cookieDomain`을 사용하고 있으므로, 환경변수만 설정하면 됩니다.
|
|
|
|
```ts
|
|
// packages/common/auth/token-manager.ts (현재 코드)
|
|
const getCookieDomain = (): string | undefined => {
|
|
if (typeof window === "undefined") return undefined;
|
|
if (config.cookieDomain) return config.cookieDomain; // ← 이미 준비됨
|
|
return undefined;
|
|
};
|
|
```
|
|
|
|
### Step 3: 백엔드 CORS 확인
|
|
|
|
백엔드가 `credentials: true`와 함께 올바른 `Access-Control-Allow-Origin`을 반환하는지 확인:
|
|
|
|
```
|
|
Access-Control-Allow-Origin: https://cs.onasys.co.kr (또는 as.onasys.co.kr)
|
|
Access-Control-Allow-Credentials: true
|
|
```
|
|
|
|
> [!WARNING]
|
|
> `Access-Control-Allow-Origin: *` 와 `credentials: true`는 동시에 사용 불가.
|
|
> 명시적 도메인 또는 동적 오리진 허용 필요.
|
|
|
|
---
|
|
|
|
## SameSite 주의사항
|
|
|
|
현재 토큰 매니저 설정:
|
|
```ts
|
|
setCookie(key, value, {
|
|
secure: isHttps,
|
|
sameSite: "lax", // ← 서브도메인 간 이동 시 쿠키 전송됨 (OK)
|
|
domain,
|
|
path: "/",
|
|
});
|
|
```
|
|
|
|
- `sameSite: "lax"` → 서브도메인 간 네비게이션에서 쿠키 전송 **가능** ✅
|
|
- `sameSite: "strict"` → 다른 도메인에서의 요청에 쿠키 미전송 ❌
|
|
- `sameSite: "none"` + `secure: true` → 크로스사이트 iframe 등에서도 전송 (필요 시)
|
|
|
|
배포 환경 HTTPS 사용 시 `secure: true` 자동 적용되므로 문제 없음.
|
|
|
|
---
|
|
|
|
## 세션 공유 흐름 (적용 후)
|
|
|
|
```
|
|
사용자가 cs.onasys.co.kr 에서 로그인
|
|
↓
|
|
setCookie("access-token", jwt, { domain: ".onasys.co.kr" })
|
|
↓
|
|
브라우저: .onasys.co.kr 쿠키 저장
|
|
↓
|
|
사용자가 as.onasys.co.kr 으로 이동
|
|
↓
|
|
브라우저가 자동으로 ".onasys.co.kr" 쿠키 전송
|
|
↓
|
|
as 앱의 미들웨어: req.cookies.get("access-token") ✅ 정상 읽힘
|
|
↓
|
|
백엔드 API 호출 시 Authorization 헤더에 토큰 포함
|
|
```
|
|
|
|
---
|
|
|
|
## 추가 고려사항
|
|
|
|
### 로그아웃 처리
|
|
한 앱에서 로그아웃 시 **도메인 지정 쿠키 삭제** 필요:
|
|
```ts
|
|
// removeToken 에서 domain 이 자동 적용됨 (현재 코드 OK)
|
|
removeCookie(key, { domain: ".onasys.co.kr", path: "/" });
|
|
```
|
|
|
|
### 토큰 갱신 (Refresh Token)
|
|
`refresh-token` 쿠키도 같은 `domain` 설정 적용 → 두 앱 모두 silent refresh 가능.
|
|
|
|
### 로컬 개발 환경
|
|
로컬에서는 `localhost`가 서브도메인을 지원하지 않으므로 별도 처리 불필요 (`.env.local`에는 설정하지 않음).
|
|
|
|
---
|
|
|
|
## 최소 변경 요약
|
|
|
|
| 위치 | 변경 내용 |
|
|
|------|----------|
|
|
| `apps/cs/.env.production` | `NEXT_PUBLIC_COOKIE_DOMAIN=.onasys.co.kr` 추가 |
|
|
| `apps/as/.env.production` | `NEXT_PUBLIC_COOKIE_DOMAIN=.onasys.co.kr` 추가 |
|
|
| 소스 코드 | **변경 없음** (이미 cookieDomain 지원 구현됨) |
|
|
| 백엔드 | CORS `Access-Control-Allow-Origin` 명시적 도메인 설정 확인 |
|