oauth2-proxy Nginx auth_request에서 API 401을 유지하는 법

증상과 정확한 오류

oauth2-proxy Nginx auth_request 구성에서 인증되지 않은 API 요청이 원래의 401 대신 302 로그인 리디렉션이나 HTML 로그인 페이지를 받을 수 있습니다. 브라우저 화면에서는 자연스러운 동작이지만, JSON을 기대하는 모바일 앱·CLI·프런트엔드 fetch에는 Unexpected token '<', CORS 오류, 무한 로그인 재시도처럼 보입니다.

auth_request는 지정한 하위 요청이 2xx를 반환하면 본 요청을 통과시키고, 401 또는 403이면 접근을 거부합니다. oauth2-proxy의 /oauth2/auth도 인증 성공 시 202, 실패 시 401을 반환하도록 마련된 endpoint입니다. 문제는 이 401 뒤에 Nginx의 공통 error_page 또는 로그인 이동 규칙이 붙어 API까지 브라우저 흐름으로 바꾸는 데 있습니다.

또 하나 구분할 것은 인증 하위 요청의 401과 실제 API upstream이 업무 규칙 때문에 반환한 401입니다. 전자는 oauth2-proxy에서 오고, 후자는 인증을 통과한 뒤 애플리케이션에서 옵니다. 상태 코드만 보고 같은 원인으로 처리하면 정상적인 API 응답까지 로그인 페이지로 가로챌 수 있습니다.

먼저 확인할 값

확인 지점명령 또는 설정판단 기준
비로그인 API 응답curl -i https://example.com/api/health401과 JSON이 필요한데 302, Location, HTML이 나오면 로그인 처리 규칙이 API에 적용된 상태입니다.
인증 하위 요청curl -i https://example.com/oauth2/auth세션이 없을 때 401, 유효한 세션 쿠키가 있을 때 202인지 확인합니다.
Nginx 최종 설정nginx -T/api/가 어느 location에 매칭되고 어떤 error_page를 상속하는지 봅니다.
하위 요청 본문proxy_pass_request_body off/oauth2/auth location에서 본문 전달을 끄고 Content-Length를 비우는지 확인합니다.
upstream 401인증된 요청의 API 로그oauth2-proxy가 202를 냈는데 API가 401이면 인증 게이트가 아니라 애플리케이션 권한 문제입니다.

/oauth2/auth를 외부에 직접 노출하지 않는 구성이라면 Nginx 또는 같은 네트워크 안에서 oauth2-proxy의 해당 endpoint를 호출합니다. 테스트 쿠키와 토큰은 명령 기록이나 공유 로그에 남기지 않습니다. nginx -T 출력에도 민감한 헤더나 upstream 정보가 있을 수 있으므로 로컬에서만 확인합니다.

원인 분기

oauth2-proxy Nginx auth_request 401 경로

  • /oauth2/auth가 세션 없이 401: 정상적인 인증 실패 계약입니다. 이 응답을 API location이 401로 유지하는지가 다음 확인 지점입니다.
  • /oauth2/auth가 404 또는 5xx: 리디렉션 정책보다 oauth2-proxy upstream 주소, proxy_pass, 경로를 먼저 고칩니다. auth_request는 401·403 이외의 응답을 인증 오류로 정상 처리하지 못합니다.
  • API 응답에 Location 헤더가 있음: 상위 server 또는 공통 include의 error_page 401, auth-signin 성격의 규칙이 API location에도 적용됐는지 확인합니다.
  • 비로그인은 401인데 인증된 요청도 401: oauth2-proxy 세션 쿠키가 전달되지 않거나, 인증은 202로 통과했지만 API 자체 권한 검사가 401을 반환한 경우입니다. oauth2-proxy 로그와 API 로그의 request ID 또는 시각을 맞춥니다.
  • 401 본문만 HTML: 상태 코드는 보존됐지만 Nginx 기본 오류 페이지를 내보낸 상태일 수 있습니다. API 계약상 JSON이 필요하면 API 전용 named location에서 최소 JSON 응답을 만듭니다.

수정 순서

  1. 현재 설정을 백업하고 펼쳐 봅니다. umask 077; nginx -T > /PRIVATE_BACKUP_DIR/nginx-before-auth-split.txt로 include가 합쳐진 설정을 보관합니다. oauth2-proxy 버전과 실행 옵션도 함께 기록하되 client secret과 cookie secret은 가립니다.
  2. 인증 하위 요청 location을 공식 패턴에 맞춥니다. location = /oauth2/auth에서 oauth2-proxy로 전달하고 proxy_pass_request_body off;, proxy_set_header Content-Length "";, 원래 요청을 식별할 헤더를 설정합니다. 이 location의 목적은 로그인 화면을 그리는 것이 아니라 202 또는 401을 반환하는 것입니다.
  3. 브라우저와 API location을 분리합니다. 기존 화면 location에는 401을 로그인으로 보내는 규칙을 유지할 수 있지만, location /api/에는 별도의 auth_request /oauth2/auth;와 API 전용 401 처리를 둡니다. 더 구체적인 /api/가 공통 location보다 우선 매칭되는지 확인합니다.
  4. API 401의 처리 목적지를 명시합니다. 아래 검증 예제처럼 error_page 401 =401 @api_unauthorized;와 같은 server의 named location을 함께 사용하고 실제 응답을 확인합니다.
  5. upstream 오류 가로채기를 확인합니다. API가 자체적으로 반환하는 401 본문을 그대로 전달해야 한다면 API location에서 광범위한 proxy_intercept_errors on; 사용을 피하고, 인증 하위 요청에서 생긴 401과 upstream 401을 로그로 구분합니다.
  6. 구문 검사 후 재적재합니다. nginx -t가 성공한 뒤에만 nginx -s reload 또는 배포 환경의 정상 reload 명령을 실행합니다. 컨테이너라면 해당 이미지가 제공하는 실행 방식과 PID 1 구성을 따릅니다.

문법 검사와 실제 응답을 나누어 확인합니다

기존에 소개한 error_page 401 =401;은 401 보존 예제로 사용하면 안 됩니다. 독립 검토자가 제공한 Nginx 1.26.3 실험에서는 문법 검사가 성공했지만 실제 요청이 302와 Location: =401을 반환했습니다. 2026-09-13 별도 개발 호스트의 Nginx 1.28.3에서도 같은 결과를 다시 확인했습니다. 두 시험 모두 운영 장애 이력이 아니라 격리된 비교 실험입니다.

공식 문법 error_page code ... [=[response]] uri;에서 상태 지정과 목적지 URI는 별개입니다. API가 필요로 하는 JSON 401 처리 목적지를 다음처럼 명시합니다. 기존 인증·쿠키·백엔드 설정을 통째로 대체하는 설정이 아닙니다.

# Inside the existing API location:
error_page 401 =401 @api_unauthorized;

# Separate named location in the same server:
location @api_unauthorized {
    default_type application/json;
    return 401 '{"error":"unauthorized"}';
}

모의 인증 서버로 확인한 결과는 아래와 같습니다. 실제 OAuth 제공자, 만료 쿠키, 세션 갱신, 여러 쿠키 조각과 WWW-Authenticate의 보존은 시험하지 않았습니다. 시험의 고정 허용·거부 헤더는 운영 인증 로직에 넣지 않습니다.

CaseStatusLocationBody
bad example302=401HTML
named location401noneJSON
auth 401401noneJSON
browser auth 401302/loginHTML
auth allow, backend 200200noneJSON
auth 403403noneHTML
auth 500500noneHTML
auth allow, backend 401401nonebackend JSON

403과 인증 서버 오류를 모두 401로 바꾸면 원인을 숨깁니다. 이 구성은 proxy_intercept_errors off;에서 백엔드 401 본문을 보존했습니다. 이 결과만으로 403·500의 JSON 응답이나 실제 로그인 왕복까지 보장하지 않습니다.

Nginx error_page · auth_request

검증과 롤백

비로그인 API 호출은 curl -i https://example.com/api/health에서 401을 반환하고, Location 헤더와 HTML 로그인 문서가 없어야 합니다. 같은 사용자가 브라우저 화면 경로를 열었을 때는 기존 로그인 흐름이 유지돼야 합니다. 두 테스트를 함께 통과해야 location 분리가 제대로 된 것입니다.

  • 유효한 세션 쿠키를 사용한 API 요청은 oauth2-proxy 202 뒤 실제 upstream 응답까지 도달하는지 확인합니다.
  • 만료되거나 없는 세션은 API에서 401, 브라우저 화면에서 의도한 로그인 이동을 만드는지 비교합니다.
  • Nginx access log에 auth subrequest 상태와 최종 요청 상태를 함께 남겨 401의 발생 계층을 확인합니다.
  • API가 자체적으로 내는 401·403 본문이 불필요하게 공통 로그인 응답으로 바뀌지 않는지 권한 부족 계정으로 테스트합니다.

변경 뒤 인증된 요청까지 막히거나 브라우저 로그인이 깨지면 nginx-before-auth-split.txt와 실제 설정 파일의 차이를 확인해 API location 변경만 되돌립니다. nginx -t를 다시 통과시킨 뒤 reload하고, oauth2-proxy의 세션 저장소나 cookie secret을 함께 바꾸지 않았는지 확인합니다.

공식 문서 및 확인 기준

이 글에 남아 있던 이전 검증 기록

작성: Nole IT 운영자

검증 수준: 공식 문서 대조

게시 시점의 oauth2-proxy endpoint 계약과 Nginx 모듈 동작을 대조했습니다. 운영 reload 전에 브라우저 경로와 API 경로를 각각 테스트하세요.

편집 및 수정 정책 · 오류 제보