---
type: how-to
title: 리버스 프록시 레시피와 콜백 계약
summary: Traefik이나 Caddy에서 프록시 게이트를 배선하고, 푼 패스를 쿠키로 바꾸는 콜백을 호스팅하고, Authelia를 건드리지 않고 게이트를 겁니다.
---

모든 리버스 프록시에는 같은 두 조각이 필요합니다. 요청이 유효한 패스를 지녔는지 Caputchin에 묻는 **인가자** 확인과, 푼 패스를 `cpt_gate` 쿠키로 바꾸는, 당신 오리진 위의 **콜백**입니다. [nginx 안내](/docs/proxy-page-gate/set-up)가 끝에서 끝까지의 형태를 보여 줍니다. 이 페이지는 다른 프록시들, 정확한 콜백 계약, 그리고 완전한 Authelia 예시를 줍니다. `YOUR_SITE_KEY`는 전체에서 당신의 공개 키로 바꾸세요.

엔드포인트는 어디서나 같습니다:

- **인가자**: `GET https://verify.caputchin.com/v1/gate/authz?site=YOUR_SITE_KEY`, 방문자의 `Cookie` 헤더를 전달하여. `204`는 허용, `401`은 챌린지를 뜻합니다.
- **챌린지**: 브라우저를 `https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return=<원래 URL>`로 리디렉션합니다.

## Traefik

인가자에는 `forwardAuth` 미들웨어를 씁니다. Traefik은 들어오는 `Cookie` 헤더를 기본으로 전달합니다. 미들웨어에서 `401`이 오면, 브라우저를 챌린지로 리디렉션합니다(`302`를 내는 작은 라우트를 가리키는 에러 페이지 미들웨어, 또는 당신의 엣지에서 `401`을 처리).

```yaml
http:
  middlewares:
    cpt-gate:
      forwardAuth:
        address: "https://verify.caputchin.com/v1/gate/authz?site=YOUR_SITE_KEY"
        # The 401 from forwardAuth is your signal to send the browser to:
        #   https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return=<original-url>
  routers:
    portal:
      rule: "Host(`auth.example.com`)"
      middlewares: ["cpt-gate"]
      service: your-app
```

## Caddy

인가자에는 `forward_auth`를, `401`을 리디렉션으로 바꾸는 데는 `handle_errors`를 씁니다. 이것은 출발점입니다. 지시어 구문은 당신의 Caddy 버전에 맞게 조정하세요.

```caddy
auth.example.com {
  forward_auth https://verify.caputchin.com {
    uri /v1/gate/authz?site=YOUR_SITE_KEY
    copy_headers Cookie
  }
  handle_errors {
    @challenge expression {http.error.status_code} == 401
    redir @challenge https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return={scheme}://{host}{uri} 302
  }
  reverse_proxy your-app:8080
}
```

## 콜백

호스팅 챌린지 페이지는 끝에 `https://<당신의-오리진>/__cpt/callback`으로 `POST` 폼을 자동 제출하며, **본문**에 두 필드, `cpt_gate`(패스)와 `to`(방문자를 어디로 보낼지)를 담습니다. 이 한 라우트를 당신 오리진에 더하세요(또는 프록시 위의 전용 location으로). 그것은 반드시:

1. `cpt_gate`와 `to`를 POST **본문**에서 읽고, URL에서는 결코 읽지 않아야 합니다(URL 안의 패스는 로그와 리퍼러로 샙니다).
2. `to`가 당신 자신의 오리진 위인지 확인하고, 그 외 무엇이든 거부해야 합니다. 그래야 콜백을 오픈 리디렉션으로 바꿀 수 없습니다.
3. 아래 속성으로 쿠키를 설정해야 합니다.
4. `to`로 (`303`으로) 리디렉션해야 합니다.

```js
// Any tiny handler on your origin works. Express shown; ~15 lines.
app.post("/__cpt/callback", express.urlencoded({ extended: false }), (req, res) => {
  const pass = String(req.body.cpt_gate || "");
  const to = String(req.body.to || "/");
  const target = new URL(to, `https://${req.headers.host}`);
  if (target.host !== req.headers.host) return res.status(400).end(); // same-origin only
  res.setHeader(
    "Set-Cookie",
    `cpt_gate=${pass}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=1800`
  );
  res.redirect(303, target.pathname + target.search);
});
```

쿠키 속성이 보안 모델의 전부입니다. 패스는 기기에 묶이지 않으므로, `HttpOnly`(스크립트 절도를 막음), `Secure`(HTTPS만), `SameSite=Lax`, 그리고 짧은 `Max-Age`(당신의 통과 TTL에 맞춤)가 그것을 보호하는 것입니다. 또한 `cpt_gate` 값을 당신의 액세스 로그에 기록하지 말고, 콜백에 `Referrer-Policy: no-referrer`를 유지해 패스가 리퍼러 헤더에 얹혀 함께 실려 가는 일이 결코 없게 하세요.

## 실전 예시: Authelia

Authelia의 로그인 포털은 위젯을 삽입할 자리도 플러그인 훅도 없는, 컴파일된 단일 페이지 앱이므로, 게이트가 그것을 보호하는 방법입니다. Authelia는 이미 리버스 프록시 뒤에 앉아 있고(그 자신의 ForwardAuth가 그렇게 동작합니다), 그 프록시가 게이트가 가는 곳입니다. Authelia 자체는 전혀 수정되지 않은 채로 남습니다.

게이트를 포털의 vhost(예컨대 `auth.example.com`)에 놓습니다:

- 포털 HTML과 1차 인증 로그인 엔드포인트(`/api/firstfactor`)에 **게이트를 겁니다**.
- Authelia 자신의 ForwardAuth verify 엔드포인트(`/api/verify`)를 **제외합니다**. 프록시는 보호된 각 업스트림 요청마다 이것을 호출합니다. 여기에 게이트를 걸면 Authelia 뒤의 보호된 각 앱이 깨집니다.
- 헬스 체크와 챌린지 페이지 자체가 필요로 하는 정적 에셋을 **제외합니다**.
- 위와 같이 `/__cpt/callback` 라우트를 **더합니다**.

게이트는 봇을 포털에서 떼어 놓습니다. Authelia 내장 Regulation(재시도 한도와 밴)은 통과한 누구에게든 자격 증명 재시도를 여전히 제한하니, 두 층이 겹칩니다. 게이트는 상호작용 포털(로그인과 동의 UI)만 보호합니다. 그것은 비상호작용 토큰 엔드포인트에 게이트를 걸지 않으며, 걸어서도 안 됩니다.

## 함께 보기

- [프록시 게이트 설정하기](/docs/proxy-page-gate/set-up): nginx 안내와 미리보기-모드-먼저 롤아웃.
- [개요](/docs/proxy-page-gate/overview): 개념과 통과 쿠키.
- [통계](/docs/proxy-page-gate/statistics): 챌린지 대 통과를 지켜보며 배선을 확인.
