---
type: how-to
title: リバースプロキシのレシピとコールバック契約
summary: Traefik や Caddy でプロキシゲートを配線し、解かれたパスを Cookie に変えるコールバックをホストし、Authelia に触れずにゲートをかけます。
---

どのリバースプロキシにも同じ二つの部品が要ります。リクエストが有効なパスを持つか Caputchin に尋ねる **オーソライザー** チェックと、解かれたパスを `cpt_gate` Cookie に変える、あなたのオリジン上の **コールバック** です。[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`（訪問者をどこへ送るか）が入ります。この一つのルートをあなたのオリジンに足してください（またはプロキシ上の専用ロケーションとして）。それは次を満たす必要があります：

1. `cpt_gate` と `to` を POST の **ボディ** から読み、URL からは決して読まない（URL の中のパスはログやリファラーに漏れます）。
2. `to` があなた自身のオリジン上だと確認し、それ以外はすべて拒否する。そうすればコールバックをオープンリダイレクトに変えられません。
3. 下の属性で Cookie をセットする。
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);
});
```

Cookie の属性がセキュリティモデルのすべてです。パスはデバイスに紐づかないので、`HttpOnly`（スクリプトによる盗みを防ぐ）、`Secure`（HTTPS のみ）、`SameSite=Lax`、そして短い `Max-Age`（通過許可 TTL に合わせる）が、それを守るものです。また、`cpt_gate` の値をアクセスログに記録しないでください。そしてコールバックに `Referrer-Policy: no-referrer` を保ち、パスがリファラーヘッダーに乗って一緒に運ばれないようにしてください。

## 実例：Authelia

Authelia のログインポータルは、ウィジェットを埋め込む場所もプラグインフックもない、コンパイル済みのシングルページアプリなので、ゲートがそれを守るやり方です。Authelia はすでにリバースプロキシの背後に座っており（自身の ForwardAuth はそう動きます）、そのプロキシがゲートの置き場所です。Authelia 自体はまったく変更されずに残されます。

ゲートをポータルの vhost（たとえば `auth.example.com`）に置きます：

- ポータルの HTML と第一要素のログインエンドポイント（`/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)：概念と通過許可 Cookie。
- [統計](/docs/proxy-page-gate/statistics)：チャレンジ対パススルーを見て配線を確かめる。
