はじめに
nginx で OpenID Connect (OIDC) 認証を実現したいとき、どのような選択肢があるでしょうか。
NGINX Plus(商用サブスクリプション)にはネイティブ OIDC モジュールが含まれていますが、OSS 版の nginx にはこの機能がありません。lua-resty-openidc や oauth2-proxy といった代替手段もありますが、nginx のアーキテクチャにネイティブ統合されたモジュールは存在しませんでした。
本記事では、NGINX Plus のネイティブ OIDC モジュール(R34〜R36 で段階的に機能拡充)を参考に開発した OSS 版 OIDC モジュールについて、その動機・機能概要・設計上の判断ポイントを紹介します。この記事を通じて、モジュールの設計思想を理解していただき、導入の判断材料にしていただければと考えます。
1. なぜ作ったのか
1.1 njs ベース実装の限界
nginx における OIDC 認証の公式リファレンス実装は、長らく njs (nginx JavaScript) をベースとしたものでした。この実装にはいくつかの課題がありました。
- 設定の複雑さ: OIDC のロジックが
mapディレクティブや njs スクリプトに分散し、設定の見通しが悪い - 外部スクリプトへの依存: IdP のメタデータ取得に外部の bash スクリプトが必要で、IdP の設定変更時に手動介入が発生する
- パフォーマンスへの懸念: リクエストごとに ID トークンの再検証が必要で、高負荷時にオーバーヘッドが生じる
1.2 NGINX Plus R34 のネイティブ OIDC モジュール
NGINX Plus R34 でネイティブ OIDC モジュール (ngx_http_oidc_module) が導入されました(F5 Community の紹介記事)。
このモジュールは njs ベース実装の課題を根本的に解決しています。
- シンプルな設定:
oidc_providerブロックとauth_oidcディレクティブだけで動作する - 自動メタデータ取得: IdP の
.well-known/openid-configurationを自動的にフェッチ・キャッシュする - セッション管理: サーバーサイドセッションにより、リクエスト毎のトークン再検証が不要
注: ネイティブ OIDC モジュールは R34 の初期リリース以降、段階的に機能が拡充されています。
リリース 主な追加機能 R34 Authorization Code Flow の基本機能。ログアウトは未実装(ロードマップ上) R35 (v1.29.0+) RP-Initiated Logout、UserInfo クレームアクセスを追加。PKCE はまだロードマップ上 R36 (v1.29.3+) Front-Channel Logout、PKCE (S256) サポートを追加 本記事で「商用版」として言及する機能は、R36 時点での最新仕様を基準としています。
設定例を見れば、その簡潔さがわかります。
oidc_provider my_idp {
issuer "https://provider.domain";
client_id "unique_id";
client_secret "unique_secret";
}
server {
auth_oidc my_idp;
location / {
proxy_set_header username $oidc_claim_sub;
proxy_pass http://backend;
}
}
1.3 OSS ユーザーの課題
ネイティブ OIDC モジュールは理想的なソリューションですが、NGINX Plus の商用サブスクリプションが必要です。OSS 版 nginx のユーザーはこの恩恵を受けられません。
この状況に対して、同等の機能を OSS として提供できれば多くのエンジニアにとって有用だと考え、本モジュールを開発しました。
本モジュールの目標:
- 商用版と同等の設定体験(
oidc_providerブロック +auth_oidcディレクティブ) - nginx のアーキテクチャ(イベント駆動・ノンブロッキング)を尊重した設計
- 実運用で必要な機能(Redis セッションストア、マルチプロバイダー、柔軟な認証モード)の提供
2. モジュール概要
2.1 対応する OIDC フロー
本モジュールは Authorization Code Flow を実装しています。これは、サーバーサイドアプリケーション(Confidential Client)における標準的な認証フローです。

PKCE (Proof Key for Code Exchange) にも対応しており、S256 コードチャレンジメソッドによる認可コード横取り攻撃の防止が可能です。
2.2 主要機能
| カテゴリ | 機能 |
|---|---|
| 認証 | Authorization Code Flow、PKCE (S256)、マルチプロバイダー |
| トークン検証 | JWT 署名検証(12 アルゴリズム: RS256/384/512, PS256/384/512, ES256/256K/384/512, Ed25519/Ed448) |
| セッション | メモリストア(共有メモリ)、Redis ストア(分散環境向け) |
| 認証モード | off / verify / require の 3 段階制御 |
| ユーザー情報 | UserInfo エンドポイント取得、$oidc_claim_* 変数によるクレーム参照 |
| ログアウト | RP-Initiated Logout |
| メタデータ | .well-known/openid-configuration の自動取得・キャッシュ |
2.3 最小構成例
Google を IdP とした場合の最小構成です。
http { # セッションストア定義 oidc_session_store memory_store { type memory; size 10m; ttl 3600; } # OIDC プロバイダー定義 oidc_provider google { issuer "https://accounts.google.com"; client_id "your-client-id.apps.googleusercontent.com"; client_secret "your-client-secret"; redirect_uri "/oauth2/callback"; } server { listen 443 ssl; server_name myapp.example.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; # 認証を有効化 auth_oidc google; location / { proxy_pass http://backend; proxy_set_header X-User-ID $oidc_claim_sub; proxy_set_header X-User-Email $oidc_claim_email; } # OIDC 外部通信用の内部プロキシロケーション(必須) location /_oidc_http_fetch { internal; resolver 127.0.0.53 valid=300s; resolver_timeout 5s; auth_oidc off; proxy_pass $oidc_fetch_url; proxy_method $oidc_fetch_method; proxy_set_header Content-Type $oidc_fetch_content_type; proxy_set_header Content-Length $oidc_fetch_content_length; proxy_set_header Authorization $oidc_fetch_bearer; proxy_set_header Host $proxy_host; proxy_set_header Accept-Encoding ""; proxy_pass_request_body on; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_ssl_verify on; proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; proxy_ssl_server_name on; proxy_ssl_name $proxy_host; proxy_connect_timeout 30s; proxy_read_timeout 30s; } } }
商用版と比較すると /_oidc_http_fetch ロケーションの設定が追加で必要です。この理由と、それがもたらすメリットについてはセクション 3 で説明します。
3. 商用版との比較
3.1 機能比較
| 観点 | 商用版 (NGINX Plus) | 本モジュール (OSS) |
|---|---|---|
| ライセンス | 商用サブスクリプション | MIT |
| 設定の簡潔さ | 内部ロケーション不要 | /_oidc_http_fetch が必要 |
| セッションストア | key-value store | メモリ / Redis(選択可能) |
| 認証モード | on / off |
off / verify / require |
| JWT アルゴリズム | 非公開 | 12 アルゴリズム明示対応 |
| 外部接続の制御 | モジュール内部で処理 | プロキシ設定で柔軟に制御可能 |
| PKCE | 対応・自動有効化 (R36+) | 対応(S256) |
| UserInfo | 対応 (R35+ / v1.29.0+) | 対応 |
| ログアウト | RP-Initiated (R35+) + Front-Channel (R36+) | RP-Initiated |
3.2 /_oidc_http_fetch が必要な理由
本モジュールが商用版ともっとも異なる点は、/_oidc_http_fetch という内部ロケーションの設定が必要なことです。
OIDC 認証フローでは、nginx が IdP に対して HTTP リクエストを発行する必要があります。
- メタデータ取得:
/.well-known/openid-configurationから IdP の設定を取得 - JWKS (JSON Web Key Set) 取得: 署名検証用の公開鍵セットを取得
- トークン交換: 認可コードをアクセストークン・ID トークンに交換
- UserInfo 取得: ユーザー情報エンドポイントからクレームを取得
しかし、nginx のモジュールから外部 HTTP リクエストを直接発行する仕組みは提供されていません。libcurl などの HTTP クライアントライブラリを利用する方法もありますが、それでは同期処理となり nginx のイベントループをブロックしてしまいます。IdP のレスポンスに時間がかかった場合、その間ワーカープロセス全体が停止し、他のリクエストの処理もできなくなります。
そこで本モジュールでは、nginx のサブリクエスト機構と内部プロキシロケーションを組み合わせて解決しています。

この方式により、外部通信中も nginx のイベントループはブロックされず、他のリクエストの処理を継続できます。
3.3 設定の柔軟性というメリット
/_oidc_http_fetch の設定が必要な点は一見デメリットに見えますが、結果として外部接続の挙動をユーザーが細かく制御できるというメリットをもたらしています。
例えば、以下の項目を nginx の標準ディレクティブで制御できます。
location /_oidc_http_fetch { # DNS リゾルバの指定 resolver 10.0.0.1 10.0.0.2 valid=300s; # タイムアウトの調整 proxy_connect_timeout 30s; proxy_read_timeout 30s; # SSL 検証の設定(開発環境では緩和も可能) proxy_ssl_verify on; proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; }
もしこれらがモジュール内部に組み込まれていた場合、調整にはソースコードの修正が必要になります。nginx の設定ファイルで制御できることは、運用上の大きな利点です。
4. 設計判断のポイント
本モジュールの開発で直面した主な設計判断を紹介します。
4.1 サブリクエスト非同期設計
課題: nginx モジュールから外部 HTTP リクエストを発行する方法がない。
選択肢:
- libcurl などの HTTP クライアントライブラリを使用(同期処理)
- サブリクエスト+内部プロキシロケーション(非同期処理)
判断: 選択肢 2 を採用。
nginx はイベント駆動・ノンブロッキングのアーキテクチャを採用しており、ワーカープロセスがブロックされると他の全リクエストの処理が停止します。libcurl を使えば実装は簡潔になりますが、外部 IdP のレスポンス時間に依存してワーカーが停止するリスクは受け入れられません。
サブリクエスト方式では、メタデータ取得・JWKS 取得・トークン交換・UserInfo 取得の 4 種類の外部通信すべてを nginx のプロキシモジュール経由で実行します。
- イベントループを維持: 外部通信中も他のリクエストを処理可能
- nginx の既存機能を活用: SSL 検証、タイムアウト、DNS 解決などをプロキシモジュールに委譲
- 設定による制御: 外部接続のパラメータを
nginx.confで調整可能
コールバックハンドラでは 11 状態のステートマシンを使い、サブリクエストの完了を待ってから次の処理ステップに進む仕組みを実装しています。
機会があれば、このサブリクエスト非同期設計の実装詳細(ステートマシンの設計、バッファチェーンからのレスポンス取得、メモリ管理の注意点等)を掘り下げます。
4.2 auth_oidc_mode verify ── 認証/非認証共存パターン
課題: 認証済みユーザーと未認証ユーザーの両方がアクセスするページをどう扱うか。
商用版の auth_oidc ディレクティブは on(認証必須)/ off(認証無効)の二択です。しかし、実際の Web サイトでは認証状態に応じて表示を変えるページが多く存在します。
- EC サイトのトップページ: 認証済みなら「ようこそ○○さん」、未認証なら「ログイン」
- ニュースサイト: 認証済みならパーソナライズされた記事一覧、未認証なら一般記事
このようなケースに対応するため、本モジュールでは 3 段階の認証モードを設計しました。
| モード | 動作 | 用途 |
|---|---|---|
require |
未認証なら IdP にリダイレクト | 保護されたページ |
verify |
セッションがあればクレームを変数にセット。なければそのまま通す | 認証/非認証共存ページ |
off |
OIDC 処理をスキップ | 公開ページ、静的ファイル |
設定例:
server { auth_oidc my_provider; auth_oidc_mode verify; # デフォルトは任意認証 # トップページ: 認証状態に応じてバックエンドに情報を渡す location / { proxy_pass http://frontend; proxy_set_header X-User-ID $oidc_claim_sub; } # 管理画面: 認証必須 location /admin { auth_oidc_mode require; proxy_pass http://admin_backend; } # 公開 API: 認証処理なし location /api/public { auth_oidc_mode off; proxy_pass http://api_backend; } }
バックエンド側では X-User-ID ヘッダーの有無で認証状態を判定し、表示を切り替えることができます。verify モードにより、認証処理を nginx 側で完結させ、バックエンドのロジックを簡素化できます。
4.3 Redis セッションストア
課題: 複数台の nginx で構成される環境でセッションを共有する方法。
本モジュールは 2 種類のセッションストアを提供しています。
メモリストア(デフォルト):
- nginx の共有メモリ上の赤黒木 (Red-Black Tree) で実装
- 単一サーバー構成向け
- 外部依存なし
Redis ストア:
- Redis をバックエンドとした分散セッション管理
- 複数台の nginx でセッションを共有可能
- ロードバランサー配下での運用に対応
oidc_session_store redis_store {
type redis;
hostname "redis.example.com";
port 6379;
database 0;
password "your-redis-password";
ttl 7200;
prefix "oidc:session:";
}
セッションストアは抽象インタフェースで設計されており、将来的に他のバックエンド(memcached など)を追加する余地も残しています。
4.4 セキュリティ設計
OIDC 認証を nginx で処理する以上、セキュリティはもっとも重要な関心事です。本モジュールでは以下の対策を実装しています。
| 脅威 | 対策 |
|---|---|
| 認可コード横取り攻撃 | PKCE (S256) による認可リクエストとトークンリクエストの紐付け |
| CSRF 攻撃 | state パラメータによるリクエスト検証 |
| リプレイ攻撃 | nonce パラメータによるトークン検証 |
| タイミング攻撃 | state/nonce のタイミングセーフ比較 |
| セッション固定攻撃 | 認証完了時のセッション ID ローテーション |
| トークン漏洩 | サーバーサイド保存(Cookie にはセッション ID のみ) |
| 署名偽装 | none アルゴリズムの明示的拒否 |
JWT の署名検証は OpenSSL 3.0 の EVP API を使用し、12 種類のアルゴリズムに対応しています。HMAC アルゴリズム(HS256/384/512)は、OIDC の公開鍵検証のユースケースでは不要なため、意図的に非対応としています。
5. クイックスタート
5.1 前提条件
- nginx ソースコード: nginx OSS
- OpenSSL: 3.0 以降
- Jansson: JSON パーシングライブラリ
- hiredis(任意): Redis セッションストアを使用する場合
5.2 ビルド
# リポジトリのクローン
git clone https://github.com/kjdev/nginx-oidc
cd nginx-oidc
# nginx ソースを取得
wget https://nginx.org/download/nginx-x.y.z.tar.gz
tar -xzf nginx-x.y.z.tar.gz
cd nginx-x.y.z
# configure の実行
./configure \
--with-compat \
--with-debug \
--with-http_ssl_module \
--with-http_v2_module \
--with-pcre \
--with-pcre-jit \
--add-dynamic-module=.. # OIDC モジュールを動的モジュールとして指定
# ビルド
make
5.3 設定
Google Cloud Console で OAuth 2.0 クライアントを作成
- アプリケーションの種類:「ウェブ アプリケーション」
- 承認済みのリダイレクト URI:
https://myapp.example.com/oauth2/callback - クライアント ID とクライアントシークレットを取得
セクション 2.3 の最小構成例を
nginx.confに記述し、以下を置換your-client-id.apps.googleusercontent.com→ 取得したクライアント IDyour-client-secret→ 取得したクライアントシークレットmyapp.example.com→ 実際のサーバー名/path/to/cert.pem→ SSL 証明書のパス/path/to/key.pem→ SSL 秘密鍵のパスhttp://backend→ バックエンドサーバーの URL127.0.0.53→ 環境に適した DNS リゾルバ
設定を検証
nginx -t
5.4 動作確認
- nginx を起動
nginx
- ブラウザで
https://myapp.example.com/にアクセス - Google のログイン画面にリダイレクトされることを確認
- Google アカウントでログイン
- 元の URL にリダイレクトされ、バックエンドのコンテンツが表示されることを確認
トラブルシューティング:
- リダイレクトループが発生する場合:
/_oidc_http_fetchロケーションにauth_oidc off;が設定されているか確認 - DNS エラーが発生する場合:
resolverの設定を環境に合わせて変更 - SSL エラーが発生する場合:
proxy_ssl_trusted_certificateのパスを確認
6. まとめ
NGINX Plus R34 で導入され R35・R36 で機能拡充されたネイティブ OIDC モジュールは、njs ベース実装の課題を解決する優れたソリューションですが、商用サブスクリプションが必要です。本モジュールは、同等の機能を OSS として提供することを目標に開発しました。
本モジュールの特徴:
- nginx アーキテクチャの尊重: サブリクエスト+内部プロキシによるノンブロッキング設計
- 柔軟な認証モード:
off/verify/requireの 3 段階で、認証/非認証共存ページにも対応 - 分散環境対応: Redis セッションストアによる複数台構成のサポート
- 幅広い IdP 対応: 12 種類の JWT 署名アルゴリズムに対応
- セキュリティ重視: PKCE、タイミングセーフ比較、セッション ID ローテーションなど
設定に /_oidc_http_fetch 内部ロケーションが必要になるのは、nginx OSS の制約ではありますが、結果として外部接続パラメータを nginx の標準ディレクティブで制御できるという利点につながったと考えます。
リポジトリ:GitHub