diff --git a/AGENTS.md b/AGENTS.md index 3f31194..c179019 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,7 +35,7 @@ Admin panel for the chocomae service (mouse-typing), extracted into a standalone - API routes must call `auth()` and return 401 if no session. - Session `maxAge` is 8 hours — do not lengthen without review. - `auth.ts` tracks failed login attempts per admin name in an in-memory `Map`. After 5 consecutive failures the account is locked out for 15 minutes. Counter clears on success. -- Session cookie name `${APP_ENV}-chocoadmin.session-token` must be set in **both** `auth.ts` (`cookies.sessionToken.name`) and `proxy.ts` (`getToken({ cookieName })`). Setting only `auth.ts` causes `proxy.ts` to look for the default cookie name, which is never written → middleware redirects every request to `/login` → login redirects back to `/` → infinite `ERR_TOO_MANY_REDIRECTS`. In NextAuth v5 the JWT encryption salt equals the cookie name, so both must match exactly. +- Session cookie name `chocoadmin-${APP_ENV}.session-token` must be set in **both** `auth.ts` (`cookies.sessionToken.name`) and `proxy.ts` (`getToken({ cookieName })`). Setting only `auth.ts` causes `proxy.ts` to look for the default cookie name, which is never written → middleware redirects every request to `/login` → login redirects back to `/` → infinite `ERR_TOO_MANY_REDIRECTS`. In NextAuth v5 the JWT encryption salt equals the cookie name, so both must match exactly. - Do **not** add `__Secure-` prefix to the cookie name. Next.js 16 middleware runs in Node.js runtime (not Edge) and receives HTTP from Nginx Proxy Manager — `__Secure-` cookies are silently rejected over HTTP, producing the same redirect loop. ## Key files diff --git a/auth.ts b/auth.ts index 4fb07be..8938750 100644 --- a/auth.ts +++ b/auth.ts @@ -70,7 +70,7 @@ declare module "next-auth/jwt" { const appEnv = process.env.APP_ENV ?? "local"; const isSecure = process.env.NODE_ENV === "production"; -const sessionCookieName = `${appEnv}-chocoadmin.session-token`; +const sessionCookieName = `chocoadmin-${appEnv}.session-token`; export const { handlers: { GET, POST }, diff --git a/docs/deployment.md b/docs/deployment.md index 28966a5..4a375b9 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -38,7 +38,7 @@ NEXTAUTH_URL="https://chocoadmin.jinaju.com" `AUTH_SECRET` and `NEXTAUTH_SECRET` must be the same strong random value within each environment for Auth.js compatibility. Use different values between production and stage. -`APP_ENV` (`production` / `stage`) is set by the `docker-compose*.yml` `environment` block — do **not** put it in the env file. It drives the session cookie name (`${APP_ENV}-chocoadmin.session-token`), which keeps production and stage cookies separate even if they share a browser. +`APP_ENV` (`production` / `stage`) is set by the `docker-compose*.yml` `environment` block — do **not** put it in the env file. It drives the session cookie name (`chocoadmin-${APP_ENV}.session-token`), which keeps production and stage cookies separate even if they share a browser. When the public URL uses HTTPS, both `AUTH_URL` and `NEXTAUTH_URL` must use the exact external HTTPS URL. @@ -181,7 +181,7 @@ Middleware redirects to `/login`, login page redirects back to `/` — infinite 1. **Check `AUTH_URL` / `NEXTAUTH_URL`** — must exactly match the public HTTPS URL including scheme. 2. **Check NPM scheme** — NPM must forward with scheme `http` (not `https`) to the container. The app detects HTTPS from `AUTH_URL`, not from the incoming request. -3. **Check cookie name consistency** — `auth.ts` (`cookies.sessionToken.name`) and `proxy.ts` (`getToken({ cookieName })`) must both use the same `${APP_ENV}-chocoadmin.session-token` value. If `proxy.ts` uses the default name while `auth.ts` uses a custom one, middleware never finds the session. +3. **Check cookie name consistency** — `auth.ts` (`cookies.sessionToken.name`) and `proxy.ts` (`getToken({ cookieName })`) must both use the same `chocoadmin-${APP_ENV}.session-token` value. If `proxy.ts` uses the default name while `auth.ts` uses a custom one, middleware never finds the session. 4. **Check for `__Secure-` prefix** — do not add it. Middleware runs in Node.js runtime and receives HTTP from Nginx. `__Secure-` cookies are silently rejected over HTTP. 5. **Clear browser cookies** for the domain, then test in a private window. 6. **Recreate the container** after changing `.env.*`. diff --git a/docs/plan/project-plan.md b/docs/plan/project-plan.md index 130aae8..c2833b7 100644 --- a/docs/plan/project-plan.md +++ b/docs/plan/project-plan.md @@ -574,7 +574,7 @@ chocoadmin/ - 동일 서비스명은 `proxy-network`에서 같은 DNS alias로 등록됨 - NPM의 `set $server "chocoadmin"` 이 두 컨테이너에 라운드로빈 → 트래픽이 production/stage 사이에 무작위 분산 - [x] **NextAuth v5 커스텀 쿠키 이름으로 production/stage 쿠키 분리** - - `auth.ts`: `cookies.sessionToken.name = "${APP_ENV}-chocoadmin.session-token"` 추가 + - `auth.ts`: `cookies.sessionToken.name = "chocoadmin-${APP_ENV}.session-token"` 추가 - `proxy.ts`: `getToken({ cookieName })` 추가 — `auth.ts`에만 적용하면 middleware가 세션 토큰을 찾지 못해 `ERR_TOO_MANY_REDIRECTS` 발생 - `__Secure-` prefix 금지: Node.js 런타임은 Nginx에서 HTTP로 수신하므로 Secure 쿠키가 무시됨 - [x] **서버 액션 인라인 클로저 제거** — 로그아웃 액션을 `app/(admin)/actions.ts` 로 추출 diff --git a/proxy.ts b/proxy.ts index 6ff8730..5247992 100644 --- a/proxy.ts +++ b/proxy.ts @@ -12,7 +12,7 @@ function usesSecureAuthCookies() { export async function proxy(request: NextRequest) { const { pathname, search } = request.nextUrl; const isPublicPath = PUBLIC_PATHS.includes(pathname); - const cookieName = `${process.env.APP_ENV ?? "local"}-chocoadmin.session-token`; + const cookieName = `chocoadmin-${process.env.APP_ENV ?? "local"}.session-token`; const token = await getToken({ req: request, secret: process.env.AUTH_SECRET ?? process.env.NEXTAUTH_SECRET,