- Published on
Ingress レベル認証 — oauth2-proxy と forward auth で SSO を適用する
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- external auth パターンとは
- なぜ oauth2-proxy なのか
- oauth2-proxy をデプロイする
- OIDC IdP との連携 (Keycloak / Google)
- ingress-nginx で forward auth を接続する
- Traefik の forwardAuth ミドルウェア
- Contour の場合
- basic auth および mTLS との比較
- セッションとクッキーを扱う
- マルチアプリ SSO を完成させる
- トラブルシューティング
- 運用とチューニング
- Gateway API との関係
- おわりに
- 参考資料
はじめに
Kubernetes クラスタに社内ツールを少しずつ載せていくと、いつかは気づく事実があります。Grafana、Kibana、ArgoCD、社内ダッシュボード、ビルドレポートのページ、そして認証機能がまったくない小さなサイドプロジェクトまで。これらすべてを「ログインした従業員だけ」が見られるようにしなければならないのに、各アプリケーションごとに認証を別々に付けるのは悪夢です。
あるアプリは OIDC をサポートし、あるアプリは basic auth しかサポートせず、あるアプリは認証機能そのものがありません。このバラバラなアプリ群に一貫した SSO(シングルサインオン)をかぶせるには、認証レイヤーをどこに置けばよいのでしょうか。答えは意外なほど単純です。トラフィックが最初に通過する地点、すなわち Ingress です。
この記事は、このブログですでに何度も扱ってきた OIDC、Keycloak、SSO の概念を改めて説明することはしません。代わりに徹底的に Ingress の観点から、external auth パターンを通じて oauth2-proxy を forward auth として接続し、アプリケーションのコードを一行も触らずに複数のアプリへ SSO をかぶせる方法に集中します。ingress-nginx、Traefik、Contour それぞれの方式の違いも比較します。
まず一つの前提を押さえておきましょう。2026 年現在、Kubernetes の Ingress API は事実上凍結(frozen)状態です。新しい機能はもう追加されず、後続の標準は Gateway API です。ingress-nginx はメンテナンスモードに近く、セキュリティの問題が時折報告されます。それでも現場には依然として Ingress ベースの認証が圧倒的に多く敷かれているので、この記事は現実の Ingress を扱いつつ、各節で Gateway API との関係を併せて押さえていきます。
external auth パターンとは
external auth(外部認証、forward auth とも呼びます)は、リバースプロキシがリクエストをバックエンドへ送る前に、別の認証サービスへ「このリクエストを通してよいか」とまず尋ねるパターンです。認証サービスが 200 を返せばプロキシはリクエストをバックエンドへ転送し、401 や 302 を返せばプロキシはユーザーをログインページへ送ります。
このパターンの核心的な価値は、認証ロジックをアプリケーションの外へ取り出すという点です。バックエンドのアプリは自分が誰に見られるのかを気にする必要がありません。その責任は Ingress と認証サービスだけが負います。
(1) GET /dashboard
┌──────────┐ ─────────────────────────────► ┌─────────────┐
│ ブラウザ │ │ Ingress │
│ │ ◄───────────────────────────── │ Controller │
└──────────┘ (6) 200 OK + バックエンド応答 └──────┬──────┘
▲ │
│ (2) サブリクエストで │
│ auth-url を呼ぶ │
│ ▼
│ ┌─────────────┐
│ (4) 302 ログインリダイレクト │ oauth2-proxy │
└──────────────────────────────────────── │ (auth svc) │
└──────┬──────┘
(3) クッキーなし -> 401 │
(5) クッキーあり -> 202 │
▼
┌─────────────┐
│ バックエンド │
└─────────────┘
流れを段階的にほどくとこうなります。
- ブラウザが保護されたパス(
/dashboard)へアクセスします。 - Ingress コントローラはバックエンドへ直接送らず、まず認証サービス(auth-url)へサブリクエストを送ります。このとき元のリクエストのクッキーとヘッダーを一緒に渡します。
- oauth2-proxy はリクエストに有効なセッションクッキーがあるかを確認します。なければ 401 を返します。
- Ingress は 401 を受け取ると、auth-signin に指定されたログイン URL へユーザーを 302 リダイレクトさせます。
- ユーザーが OIDC ログインを終えて再びアクセスすると、今度は有効なクッキーがあるので oauth2-proxy は 202(または 200)を返し、同時にユーザー情報をヘッダーに載せて返します。
- Ingress はそのヘッダーをバックエンドへ転送しつつ、元のリクエストをバックエンドへ送ります。
ここで重要な違いを一つ押さえておきます。forward auth で認証サービスが受け取るのは「サブリクエスト」であって、実際のリクエスト本文ではありません。認証サービスはメソッドとパス、ヘッダー(特にクッキー)だけを見て通すかどうかを決めます。だから認証サービスは軽く素早く応答できなければなりません。
なぜ oauth2-proxy なのか
forward auth の認証サービスの席には何でも置けます。自作の小さな HTTP サーバーでもよいし、Authelia や Authentik のような統合ソリューションでもかまいません。しかし最も広く使われているのは oauth2-proxy です。理由は単純です。
- OIDC と OAuth2 を標準的にサポートするので、Keycloak、Google、GitHub、Azure AD などほぼすべての IdP と連携できます。
- セッションをクッキーに保存するか Redis に保存でき、スケーラビリティの選択肢が広いです。
- forward auth モード(
--upstreamを省略し/oauth2/authエンドポイントだけを使う)を標準でサポートします。 - 認証後にユーザー情報(メール、グループなど)を応答ヘッダーとしてバックエンドへ渡せます。
oauth2-proxy は二つのモードで動作できます。一つは自分がバックエンドの前段のプロキシとなってすべてのトラフィックを受けるモード、もう一つは Ingress の後ろで認証判定だけを担当する forward auth モードです。Ingress レベルの SSO では後者を使います。Ingress がトラフィックのルーティングを担い、oauth2-proxy は /oauth2/auth という一つのエンドポイントで通すかどうかだけを応答します。
oauth2-proxy をデプロイする
まず oauth2-proxy をクラスタへデプロイします。Keycloak を IdP として使うと仮定しましょう。Helm チャートを使うのが最も手軽ですが、動作を明確に見るために核心となる設定だけを直接見ていきます。
# oauth2-proxy-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: oauth2-proxy
namespace: auth
spec:
replicas: 2
selector:
matchLabels:
app: oauth2-proxy
template:
metadata:
labels:
app: oauth2-proxy
spec:
containers:
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.6.0
args:
- --provider=oidc
- --oidc-issuer-url=https://keycloak.example.com/realms/internal
- --email-domain=*
- --upstream=static://200
- --http-address=0.0.0.0:4180
- --reverse-proxy=true
- --cookie-secure=true
- --cookie-domain=.example.com
- --whitelist-domain=.example.com
- --set-xauthrequest=true
- --pass-access-token=true
env:
- name: OAUTH2_PROXY_CLIENT_ID
valueFrom:
secretKeyRef:
name: oauth2-proxy-secret
key: client-id
- name: OAUTH2_PROXY_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: oauth2-proxy-secret
key: client-secret
- name: OAUTH2_PROXY_COOKIE_SECRET
valueFrom:
secretKeyRef:
name: oauth2-proxy-secret
key: cookie-secret
ports:
- containerPort: 4180
ここで注目すべき引数を挙げておきます。
--upstream=static://200: forward auth モードでは oauth2-proxy は実際のバックエンドへプロキシしません。認証だけを判定するので static な応答を upstream に置きます。--reverse-proxy=true: Ingress の後ろにいるので X-Forwarded ヘッダーを信頼するようにオンにします。--cookie-domain=.example.com: クッキードメインを上位ドメインに設定すると、複数のサブドメインのアプリが同じセッションクッキーを共有します。これがマルチアプリ SSO の核心です。--set-xauthrequest=true: 認証応答に X-Auth-Request-User、X-Auth-Request-Email などのヘッダーを載せて返します。バックエンドがユーザー情報を受け取れるようにします。
クッキーシークレットは必ず 32 バイトで生成しなければなりません。次のように作れます。
# cookie secret の生成 (16, 24, 32 バイトのうち 32 バイト推奨)
openssl rand -base64 32 | head -c 32 | base64
サービスも併せて作ります。
# oauth2-proxy-service.yaml
apiVersion: v1
kind: Service
metadata:
name: oauth2-proxy
namespace: auth
spec:
selector:
app: oauth2-proxy
ports:
- name: http
port: 4180
targetPort: 4180
OIDC IdP との連携 (Keycloak / Google)
oauth2-proxy が IdP と正常に連携するには、IdP 側にクライアント(アプリケーション)を登録し、リダイレクト URL を正確に指定しなければなりません。oauth2-proxy のコールバックパスは既定で /oauth2/callback です。
Keycloak では次のように設定します。
- realm を一つ選ぶか新しく作ります(例: internal)。
- クライアントを作成し、クライアントタイプを OpenID Connect にします。
- Valid redirect URIs に oauth2-proxy のコールバックアドレスを登録します。例:
https://auth.example.com/oauth2/callback - Client authentication をオンにして confidential クライアントにし、発行されたシークレットを oauth2-proxy のシークレットに入れます。
グループベースのアクセス制御をしたい場合は、Keycloak で groups クレームをトークンに含めるマッパーを追加し、oauth2-proxy に --allowed-group 引数を与えます。
# 特定のグループだけ許可
- --allowed-group=/platform-team
- --oidc-groups-claim=groups
Google を IdP として使う場合は issuer URL が固定されています。
- --provider=google
- --oidc-issuer-url=https://accounts.google.com
- --email-domain=mycompany.com
Google はグループ情報を OIDC トークンに既定では含めないので、特定ドメインのメールだけを許可する --email-domain 方式や、別途のサービスアカウントを通じた Google Groups 照会方式を使う必要があります。この違いは IdP 選択時に必ず考慮すべきです。
ingress-nginx で forward auth を接続する
さて核心です。ingress-nginx ではアノテーション二つで forward auth を有効にします。
# protected-app-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: grafana
namespace: monitoring
annotations:
nginx.ingress.kubernetes.io/auth-url: 'https://auth.example.com/oauth2/auth'
nginx.ingress.kubernetes.io/auth-signin: 'https://auth.example.com/oauth2/start?rd=$escaped_request_uri'
nginx.ingress.kubernetes.io/auth-response-headers: 'X-Auth-Request-User, X-Auth-Request-Email, X-Auth-Request-Groups'
spec:
ingressClassName: nginx
rules:
- host: grafana.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: grafana
port:
number: 3000
アノテーションの意味をほどいてみます。
auth-url: 認証サブリクエストを送るアドレスです。oauth2-proxy の/oauth2/authエンドポイントです。このエンドポイントはクッキーが有効なら 202、そうでなければ 401 を返します。auth-signin: 401 を受け取ったときにユーザーを送るログイン URL です。rdパラメータに元々行こうとしていたアドレスを載せ、ログイン後にその場所へ戻れるようにします。auth-response-headers: 認証サービスが返したヘッダーのうち、バックエンドへ渡すものを指定します。バックエンドのアプリはこのヘッダーでユーザーを識別できます。
ここで oauth2-proxy 自体も外部からアクセス可能でなければなりません。コールバックとログイン開始のエンドポイントがブラウザからアクセスされる必要があるからです。そこで oauth2-proxy 用の Ingress を別に置きます。
# oauth2-proxy-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: oauth2-proxy
namespace: auth
spec:
ingressClassName: nginx
rules:
- host: auth.example.com
http:
paths:
- path: /oauth2
pathType: Prefix
backend:
service:
name: oauth2-proxy
port:
number: 4180
この構造では、すべての保護対象アプリの Ingress が同じ auth.example.com/oauth2/auth を指します。クッキードメインが .example.com なので、一度ログインすればすべてのサブドメインのアプリでセッションが保たれます。これがマルチアプリ SSO が完成する地点です。
Traefik の forwardAuth ミドルウェア
Traefik はアノテーションではなくミドルウェア(Middleware)CRD で forward auth を表現します。哲学が少し異なります。Traefik では認証を「ミドルウェア」という再利用可能な部品として定義し、ルーターに取り付けます。
# traefik-forwardauth-middleware.yaml
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: oauth2-forwardauth
namespace: auth
spec:
forwardAuth:
address: 'http://oauth2-proxy.auth.svc.cluster.local:4180/oauth2/auth'
trustForwardHeader: true
authResponseHeaders:
- X-Auth-Request-User
- X-Auth-Request-Email
- X-Auth-Request-Groups
このミドルウェアを IngressRoute に接続します。
# traefik-ingressroute.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: grafana
namespace: monitoring
spec:
entryPoints:
- websecure
routes:
- match: Host(`grafana.example.com`)
kind: Rule
services:
- name: grafana
port: 3000
middlewares:
- name: oauth2-forwardauth
namespace: auth
Traefik の forwardAuth と ingress-nginx の auth-url は概念的には同じですが、いくつかの違いがあります。
- ingress-nginx は認証失敗時に auth-signin アノテーションを通じて直接 302 リダイレクトを処理します。Traefik の forwardAuth は認証サービスが返した応答(例: 401、または認証サービスが直接送る 302)をそのままクライアントへ伝えます。だから oauth2-proxy 側で適切にリダイレクトを作るように設定する必要があります。
- Traefik のミドルウェアは一度定義すれば複数の IngressRoute で再利用するのが非常にきれいです。マルチアプリ環境ではこの再利用性が大きな利点です。
Contour の場合
Contour は Envoy ベースのコントローラで、external authorization を Envoy の ext_authz フィルタを通じて実装します。Contour では ExtensionService と HTTPProxy を組み合わせます。gRPC ベースの ext_authz を使うのが定石ですが、oauth2-proxy を付ける際には HTTP ベースで連携するパターンを使うこともあります。
# contour-httpproxy.yaml
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: grafana
namespace: monitoring
spec:
virtualhost:
fqdn: grafana.example.com
authorization:
extensionRef:
name: oauth2-proxy-authz
namespace: auth
routes:
- conditions:
- prefix: /
services:
- name: grafana
port: 3000
Contour は ext_authz を Envoy レベルで処理するので性能特性が異なり、gRPC 認証サーバーを使えばより効率的です。ただし oauth2-proxy は既定で HTTP 認証を提供するので、Contour 環境ではアダプタの役割をする別コンポーネントが必要になることがあります。三つのコントローラを一つの表で比較するとこうなります。
| 項目 | ingress-nginx | Traefik | Contour |
|---|---|---|---|
| 認証の接続方式 | auth-url アノテーション | forwardAuth ミドルウェア CRD | HTTPProxy authorization + ext_authz |
| リダイレクト処理 | auth-signin が直接 302 | 認証サービスの応答を伝達 | Envoy ext_authz の応答処理 |
| 再利用性 | Ingress ごとにアノテーション反復 | ミドルウェアを 1 回定義し再利用 | ExtensionService を再利用 |
| データプレーン | nginx | Traefik (Go) | Envoy |
| 推奨認証プロトコル | HTTP サブリクエスト | HTTP forward auth | gRPC ext_authz を好む |
basic auth および mTLS との比較
Ingress レベルでアクセスを制限する方法は forward auth だけではありません。最も単純なのは basic auth で、最も強力なのは mTLS です。それぞれの位置づけを理解すると、forward auth をいつ使うべきかが明確になります。
basic auth はアノテーション一つ二つとシークレット一つで終わります。
# basic-auth-ingress.yaml
metadata:
annotations:
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: basic-auth
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required'
非常に簡単ですが限界が明確です。ユーザーごとの識別が難しく、パスワードのローテーションが面倒で、SSO が不可能で、グループベースの権限制御がありません。少しの間ブロックしておく一時的な保護用途には問題ありませんが、本番の SSO の代替にはなりません。
mTLS は正反対で非常に強力です。クライアントが有効な証明書を提示してこそ通過できます。人ではなくマシン間(サービス間)通信や、セキュリティ等級が非常に高い内部システムに適しています。ただしユーザー端末ごとに証明書を配布し管理するコストが大きく、ブラウザの UX が親しみやすくありません。
| 方式 | ユーザー識別 | SSO | グループ権限 | 導入難易度 | 適した状況 |
|---|---|---|---|---|---|
| basic auth | 弱い | 不可 | なし | 非常に低い | 一時的な保護、社内デモ |
| forward auth (oauth2-proxy) | 強い | 可能 | 可能 | 中 | 従業員向けの多数の Web アプリ |
| mTLS | 証明書ベース | 該当なし | 証明書ポリシー | 高い | マシン間通信、高セキュリティ領域 |
まとめると、人がブラウザでアクセスする社内 Web アプリケーション多数に一貫したログインをかぶせたいなら、forward auth が最適です。basic auth は弱すぎ、mTLS は人間のユーザーには過剰です。
セッションとクッキーを扱う
forward auth SSO の安定性は事実上クッキー管理にかかっています。いくつかの核心ポイントを整理します。
クッキードメイン。 マルチアプリ SSO の核心です。--cookie-domain=.example.com のように上位ドメインに設定してこそ、grafana.example.com と kibana.example.com が同じセッションクッキーを共有します。ドメインが異なると(例: example.com と example.net)、同じ oauth2-proxy でも SSO になりません。
クッキーストア。 oauth2-proxy はセッションをクッキー自体に入れる(クッキーセッション)か、Redis に保存できます。トークンが大きくてクッキーサイズの上限(通常 4KB)を超えると、クッキーが切られて認証が壊れます。グループクレームが多かったりアクセストークンを入れたりすると容易に上限を超えるので、この場合は Redis セッションストアへ切り替えるのが安全です。
# Redis セッションストアの使用
- --session-store-type=redis
- --redis-connection-url=redis://redis.auth.svc.cluster.local:6379
クッキーのセキュリティ属性。 本番では --cookie-secure=true(HTTPS 専用)、そして可能なら --cookie-samesite=lax を推奨します。OIDC リダイレクトのフローは外部 IdP へ行って戻ってくるので、SameSite を strict にするとコールバック時にクッキーが載らずログインループに陥ることがあります。
セッションの有効期限。 --cookie-expire と --cookie-refresh でセッションの寿命と更新の間隔を制御します。refresh を適切に設定すれば、アクセストークンが失効してもリフレッシュトークンで静かに更新され、ユーザー体験が途切れません。
マルチアプリ SSO を完成させる
ここまでの断片を集めるとマルチアプリ SSO の絵が完成します。核心となる原則は三つです。
- 一つの oauth2-proxy を共有する。 アプリごとに oauth2-proxy を別々に置きません。一つだけ置き、すべてのアプリの Ingress が同じ auth-url を指します。
- クッキードメインを上位ドメインに置く。 すべてのアプリが同じ上位ドメインの下のサブドメインであってこそクッキーを共有します。
- アプリごとの認可(authorization)は別に処理する。 SSO で「ログインしたか」は共有されますが、「このアプリを見る権限があるか」はアプリごとに異なることがあります。oauth2-proxy のグループ検査をアプリごとに変えるか、バックエンドが渡されたグループヘッダーで判断するようにします。
┌──────────────────────┐
│ oauth2-proxy (1 個) │
│ auth.example.com │
│ クッキードメイン=.example.com │
└──────────┬───────────┘
│ /oauth2/auth
┌─────────────┬───────────┼───────────┬─────────────┐
▼ ▼ ▼ ▼ ▼
grafana.ex.. kibana.ex.. argo.ex.. wiki.ex.. reports.ex..
(Ingress) (Ingress) (Ingress) (Ingress) (Ingress)
auth-url ─────┴── すべて同じ auth-url を指す ────┴──────┘
一度ログインすれば五つのアプリすべてでセッションが保たれます。ユーザーは grafana にログインした後 kibana へ移動するとき、再びログインする必要がありません。これが SSO のユーザー体験の本質です。
トラブルシューティング
forward auth SSO を初めて付けると、ほぼ必ず一つ二つの問題に出会います。よく遭遇するものを整理します。
無限リダイレクトループ。 最もよくある問題です。ログインしたのに、ずっとログインページに戻ります。原因はたいていクッキードメインの不一致か SameSite 設定の問題です。auth-signin のドメインとクッキードメインが合っているか、SameSite が strict で厳しすぎないかを確認します。
401 がバックエンドまで伝達される。 auth-url サブリクエストが 401 を返したのに auth-signin が動作しないと、ユーザーに 401 がそのまま見えます。auth-signin アノテーションのスペルと URL を再確認します。
ヘッダーがバックエンドに伝達されない。 バックエンドが X-Auth-Request-Email を受け取れないなら二つを確認します。oauth2-proxy に --set-xauthrequest=true がオンになっているか、そして Ingress の auth-response-headers にそのヘッダー名が明示されているかです。両方そろってこそヘッダーが流れます。
クッキーが大きすぎて認証が壊れる。 ログインはできるのにページを再読み込みすると再びログインしろと言われます。トークンが 4KB を超えてクッキーが切られた可能性が高いです。Redis セッションストアへ切り替えます。
大容量リクエスト本文で認証タイムアウト。 大きなファイルアップロードのパスでサブリクエスト処理が遅延することがあります。ingress-nginx では auth-request 関連のバッファとタイムアウトの設定を点検します。
# oauth2-proxy のログで拒否理由を確認
kubectl logs -n auth deploy/oauth2-proxy -f | grep -i "denied\|401\|error"
# サブリクエストが実際にどこへ行くか nginx 設定を確認
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- cat /etc/nginx/nginx.conf | grep -A5 auth_request
運用とチューニング
forward auth はすべての保護リクエストごとにサブリクエストを一度ずつ余計に発生させます。つまり oauth2-proxy はトラフィックの核心経路に置かれます。運用時には次を押さえるべきです。
- 可用性。 oauth2-proxy が落ちるとすべての保護アプリが一緒に塞がれます。replicas を 2 以上に置き、PodDisruptionBudget を設定します。
- セッションストアの可用性。 Redis セッションを使うなら Redis も単一障害点です。HA 構成を検討します。
- レイテンシ。 サブリクエストが追加の遅延を作ります。oauth2-proxy を同じクラスタ内、可能なら同じアベイラビリティゾーンに置いてネットワークの往復を減らします。
- キャッシング。 ingress-nginx は同一セッションに対する auth 応答を短くキャッシュできます。ただしセキュリティと鮮度の間のトレードオフを理解して適用しなければなりません。
# 認証応答のキャッシュ (ingress-nginx) - 注意して使用
metadata:
annotations:
nginx.ingress.kubernetes.io/auth-cache-key: '$cookie_oauth2_proxy'
nginx.ingress.kubernetes.io/auth-cache-duration: '200 202 5m, 401 30s'
Gateway API との関係
先に述べたように Ingress API は凍結状態で、Gateway API が後続の標準です。それでは forward auth はどうなるのでしょうか。
現在 Gateway API のコア仕様には external auth が標準化されていません。認証/認可はまだコントローラの実装ごとに拡張として提供されるか、ext_authz のようなデータプレーンのメカニズムに依存します。Gateway API には認証ポリシーを表現するためのポリシーアタッチメント(policy attachment)パターンが議論されており、一部の実装は BackendTLSPolicy や別途の認証ポリシー CRD を提供します。
実務的に見れば、いま ingress-nginx のアノテーションで構築した forward auth は Gateway API へ移行する際にそのまま移りません。コントローラごとのポリシー CRD や Envoy ext_authz で再構成しなければならない可能性が高いです。だから新しいクラスタを設計するなら、oauth2-proxy という認証サービス自体はそのまま残し、接続方式だけを Gateway API へ変えられるように、認証サービスとルーティング設定を緩く結合しておくのが賢明です。oauth2-proxy はどちらにせよ再利用可能な資産です。
おわりに
Ingress レベル認証の本質は「認証の責任をアプリケーションからインフラへ移すこと」です。forward auth と oauth2-proxy を組み合わせれば、認証機能がまったくないアプリにもコードを一行も触らずにエンタープライズ級の SSO をかぶせられます。
核心を改めて整理するとこうです。external auth パターンは、Ingress がバックエンドの前で認証サービスへサブリクエストを投げる構造であり、oauth2-proxy がその認証サービスとして最も広く使われています。ingress-nginx はアノテーション、Traefik はミドルウェア、Contour は ext_authz で同じパターンを別々に表現します。クッキードメインを上位ドメインに置き、oauth2-proxy 一つを共有すれば、マルチアプリ SSO が完成します。
最後に 2026 年の現実を忘れないでください。Ingress API は凍結され、ingress-nginx はメンテナンスモードです。いま forward auth を構築しつつ、認証サービスとルーティングを緩く結合して Gateway API 時代への移行を前もって準備するのが賢明な選択です。