버그 #601
진행중[버그] edge.alphea.dev 응답의 Access-Control-Allow-Origin 중복으로 Hub 웹의 모든 Connect RPC 가 브라우저에서 차단됨 (구글 로그인 불가)
0%
설명
edge.alphea.dev 가 실제 응답에 Access-Control-Allow-Origin 헤더를 두 번 내려보내 브라우저가 응답을 차단합니다. 그 결과 hub.alphea.dev 에서 모든 Connect RPC 호출이 실패하며, 구글 로그인이 콜백 단계에서 끝나지 않습니다. 게이트웨이 측 확인을 요청드립니다.
현상¶
구글 계정 선택까지는 정상 진행되고 https://hub.alphea.dev/auth/google/callback?state=... 로 정상 복귀하지만, 화면이 "Finishing Google sign-in" 에서 멈추고 아래 메시지가 표시됩니다.
Connect could not be reached. Try again shortly.
재현 환경¶
- staging (
https://hub.alphea.dev→https://edge.alphea.dev) - 확인 일시: 2026-08-11
- 브라우저: Chrome (데스크톱)
- Hub Web 커밋: 695784f (main)
브라우저 콘솔 에러 로그¶
Access to fetch at 'https://edge.alphea.dev/alphea.connect.v1.AuthService/GoogleLogin' from origin 'https://hub.alphea.dev' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'https://hub.alphea.dev, https://hub.alphea.dev', but only one is allowed. Have the server send the header with a valid value. edge.alphea.dev/alphea.connect.v1.AuthService/GoogleLogin:1 Failed to load resource: net::ERR_FAILED
값은 동일하지만 헤더가 두 개 존재하며, CORS 규격상 Access-Control-Allow-Origin 은 정확히 하나여야 하므로 브라우저가 응답 전체를 차단합니다.
확인한 내용 — 실제 응답 헤더가 중복입니다¶
POST /alphea.connect.v1.AuthService/GoogleLogin · Origin: https://hub.alphea.dev
$ curl -s -D - -o /dev/null -X POST \
"https://edge.alphea.dev/alphea.connect.v1.AuthService/GoogleLogin" \
-H "Origin: https://hub.alphea.dev" \
-H "Content-Type: application/json" -d '{}' \
| grep -i "access-control\|vary" | sort | uniq -c
2 Access-Control-Allow-Origin: https://hub.alphea.dev ← 중복
2 Access-Control-Expose-Headers: Grpc-Status, Grpc-Message,
Grpc-Status-Details-Bin,
Connect-Protocol-Version ← 중복
2 Vary: Origin ← 중복
RequestEmailChallenge 도 동일하게 중복됩니다. 즉 구글 로그인만의 문제가 아니라 이메일 OTP 를 포함한 모든 Connect RPC 가 브라우저에서 똑같이 차단됩니다.
preflight 는 정상이라 더 위험합니다¶
OPTIONS 는 헤더가 하나뿐이라 통과합니다.
$ curl -s -D - -o /dev/null -X OPTIONS \
"https://edge.alphea.dev/alphea.connect.v1.AuthService/GoogleLogin" \
-H "Origin: https://hub.alphea.dev" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: content-type" \
| grep -i "access-control\|vary" | sort | uniq -c
1 Access-Control-Allow-Headers: content-type
1 Access-Control-Allow-Methods: POST
1 Access-Control-Allow-Origin: https://hub.alphea.dev ← 여기는 정상 1개
1 Access-Control-Max-Age: 86400
1 Vary: Origin, Access-Control-Request-Method, ...
그래서 다음 순서로 진행됩니다.
- preflight 통과 → 브라우저가 실제 POST 를 전송
- 서버는 요청을 정상 처리 (이 시점에 구글 인가 코드가 소모됨)
- 응답은 돌아오지만 브라우저가 ACAO 중복으로 차단 →
fetch가 reject - SDK 가 이를
unavailable로 매핑 → 화면에 "Connect could not be reached"
인가 코드는 이미 소모된 뒤라 재시도해도 동일하게 실패합니다.
원인 위치 — 두 계층이 각각 CORS 를 붙이고 있습니다¶
Origin 을 바꿔 가며 어느 계층이 헤더를 붙이는지 분리했습니다.
Origin: https://hub.alphea.dev → ACAO 2개, Vary: Origin 2개 Origin: https://example.com → ACAO 0개, Vary: Origin 2개 ← 양쪽 모두 거부 Origin 헤더 없음 → ACAO 0개, Vary: Origin 2개
Vary: Origin 이 어떤 경우에도 항상 2개라는 점이 결정적입니다. 두 계층이 모두 CORS 로직을 실행하고 있고, 양쪽 다 동일한 origin 허용목록을 각자 들고 각자 헤더를 붙이는 중복 구성입니다.
-
앞단 openresty (응답 헤더
Server: openresty,X-Served-By: edge.alphea.dev) — 설정 위치를 특정하지 못했습니다. Network/인프라 소유로 보입니다. -
업스트림 alphea-rpc (
alphea-edge-core/alphea-rpc/internal/server/server.go:94,rs/cors) — 응답의Access-Control-Expose-Headers값이 이 코드의ExposedHeaders목록과 정확히 일치하므로 중복 헤더 중 하나는 여기서 나옵니다.
corsMiddleware := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
AllowedHeaders: []string{"*"},
ExposedHeaders: []string{"Grpc-Status", "Grpc-Message",
"Grpc-Status-Details-Bin", "Connect-Protocol-Version"},
AllowCredentials: false,
MaxAge: 86400,
})
다만 코드상 AllowedOrigins 는 "*" 인데 실제 배포본은 example.com 을 거부했으므로, 운영 중인 인스턴스는 환경 설정의 origin 허용목록으로 구동 중입니다. alphea-edge-core 레포 내 hub.alphea.dev 문자열 검색은 0건이었습니다.
제안 — 한쪽만 남기면 됩니다¶
A안 (권장) alphea-rpc 를 CORS 단일 출처로 두고, openresty 에서 CORS add_header 를 제거합니다. origin 허용목록 로직이 애플리케이션에 이미 있고, Grpc-* expose 헤더처럼 프로토콜을 아는 값은 프록시가 아니라 서버가 관리하는 편이 맞습니다.
B안 openresty 를 단일 출처로 두어야 한다면, 프록시에서 업스트림 헤더를 숨깁니다.
proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Vary;
함께 요청드립니다 — X-Alphea-Request-Id 노출¶
Access-Control-Expose-Headers 에 X-Alphea-Request-Id 가 빠져 있습니다. 이 때문에 SDK 가 trace id 를 읽지 못하고 오류 화면에도 표시되지 않아, 장애 발생 시 서버 로그와 대조할 수단이 없습니다. 이번 수정 시 함께 추가해 주시면 좋겠습니다.
수정 후 검증 방법¶
$ curl -s -D - -o /dev/null -X POST \
"https://edge.alphea.dev/alphea.connect.v1.AuthService/GoogleLogin" \
-H "Origin: https://hub.alphea.dev" \
-H "Content-Type: application/json" -d '{}' \
| grep -ci "^access-control-allow-origin"
1 ← 1 이 나와야 정상 (현재는 2)
참고 — 정상 확인된 항목¶
Hub Web 측에는 수정할 부분이 없음을 함께 확인했습니다.
| 항목 | 상태 |
|---|---|
배포 번들의 VITE_CONNECT_BASE_URL
|
정상 (https://edge.alphea.dev) |
| 배포 번들의 Google client id | 정상 |
| Connect 엔드포인트 도달성 | 정상 (400 invalid_argument 정상 응답) |
| preflight CORS | 정상 |
| 실제 응답 CORS 헤더 | 중복 — 본 이슈 |