mod_auth_openidc で OpenID Connect (OIDC) 認証を --- 4. マルチプロバイダ編

Apache HTTP Server + [mod\_auth\_openidc](https://github.com/zmartzone/mod_auth_openidc) で OpenID Connect (OIDC) 認証が必要な Web サイトを作ってみます。

本記事は「[1. 準備編](https://qiita.com/trueroad/items/1bcc1870d0b03e6f5029)」「[2. d アカウント・コネクト編](https://qiita.com/trueroad/items/3562be88b1d48130c18c)」「[3. Google アカウント編](https://qiita.com/trueroad/items/593870ebafb675196e88)」に続く「4. マルチプロバイダ編」です。d アカウント・コネクト編や Google アカウント編では、d アカウント・コネクトや Google アカウントでソーシャルログインできましたが、どちらを使うか設定によって切り替える必要があり、ユーザが選ぶことはできませんでした。本記事は Google アカウント編の設定が終わっている状態から、どちらでログインするかユーザが自分で選べるようにしてみます。

もしかしたらマズい設定などがあるかもしれません。気が付いた方はご指摘いただけるとありがたいです。

## mod\_auth\_openidc

d アカウント・コネクト編で作った `/etc/apache2/mods-available/auth_openidc_d_account_connect.conf` や Google アカウント編で作った `/etc/apache2/mods-available/auth_openidc_google_account.conf` のように、マルチプロバイダ用の設定ファイルを `/etc/apache2/mods-available/auth_openidc_multiple_provider.conf` に作ります。

### マルチプロバイダ用設定ファイル

設定項目は OIDCMetadataDir だけです。マルチプロバイダ用のメタデータを置くディレクトリを指定します。とりあえずデフォルトの設定ファイル `/etc/apache2/mods-available/auth_openidc.conf` でコメントアウトで設定されていたディレクトリをそのまま指定します。これを `/etc/apache2/mods-available/auth_openidc_multiple_provider.conf` に置きます。

```auth_openidc_multiple_provider.conf
OIDCMetadataDir /var/cache/apache2/mod_auth_openidc/metadata
```

### 各 OIDC プロバイダのメタデータ

メタデータ用のディレクトリに各 OIDC プロバイダ (OP) の情報を置きます。1 つの OP につき下記 3 つのファイルを置きます。ファイル名の `<urlencoded-issuer-value-with-https-prefix-and-trailing-slash-stripped>` の部分は OP の issuer 文字列（ID トークンの iss クレームの文字列）から、最初の `https://` と（あれば）最後の `/` を取って URL エンコードした文字列です。

* `<urlencoded-issuer-value-with-https-prefix-and-trailing-slash-stripped>.provider`
    + OpenID Connect Discovery で得られた json そのもの
* `<urlencoded-issuer-value-with-https-prefix-and-trailing-slash-stripped>.client`
    + クライアント ID とクライアントシークレットが入った json
* `<urlencoded-issuer-value-with-https-prefix-and-trailing-slash-stripped>.conf`
    + OP 毎に変更する設定値の json

#### Google アカウント

まずは OpenID Connect Discovery の json を取得します。ファイル名は仮に `google.json` にしておきます。

```
$ curl https://accounts.google.com/.well-known/openid-configuration -o google.json
```

ここから issuer の文字列を取り出し、最初の `https://` と（あれば）最後の `/` を取った文字列を求めます。

```
$ cat google.json | jq .issuer | sed 's|^"||;s|"$||;s|^https://||;s|/$||'
accounts.google.com

```

`accounts.google.com` が得られたので、これをさらに URL エンコードします（この場合は変わりませんけども）。

```
$ echo -n 'accounts.google.com' | jq -s -R -r @uri
accounts.google.com

```

というわけで最終的に `accounts.google.com` が得られました。ので `google.json` を `accounts.google.com.provider` にリネームします。

```
$ mv google.json accounts.google.com.provider
```

次に `accounts.google.com.client` としてクライアント ID とクライアントシークレットが入った json を用意します。以下のようにします。

```accounts.google.com.client
{
    "client_id":
    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
    "client_secret":
    "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
```

最後に `accounts.google.com.conf` として OP 毎に変更する設定値を用意します。以下のようにしてみました。

```accounts.google.com.conf
{
    "scope": "openid email profile",
    "response_type": "code",
    "pkce_method": "S256"
}
```

以上の 3 ファイル `accounts.google.com.provider`, `accounts.google.com.client`, `accounts.google.com.conf` をメタデータディレクトリ `/var/cache/apache2/mod_auth_openidc/metadata` に入れておきます。なお、このディレクトリと 1 つ上のディレクトリ（`/var/cache/apache2/mod_auth_openidc`）は最初からオーナーが `www-data:www-data` 、パーミッションが 700 になっていましたので、今回入れるファイルも同オーナー、パーミッション 600 にしておきます。

```
$ sudo cp accounts.google.com.* /var/cache/apache2/mod_auth_openidc/metadata/
$ sudo chown www-data:www-data /var/cache/apache2/mod_auth_openidc/metadata/accounts.google.com.*
$ sudo chmod 600 /var/cache/apache2/mod_auth_openidc/metadata/accounts.google.com.*
```

#### d アカウント・コネクト

d アカウント・コネクトは OpenID Connect Discovery に対応しているか否かよくわからなかったので、 https://github.com/auth0/passport-daccount の設定値や [2. d アカウント・コネクト編](https://qiita.com/trueroad/items/3562be88b1d48130c18c) の設定値、そして手動で ID トークンを取得してみた時の挙動などから適当に以下のように作ってみました。

```d_account_connect.json
{
 "issuer": "https://conf.uw.docomo.ne.jp/",
 "authorization_endpoint": "https://id.smt.docomo.ne.jp/cgi8/oidc/authorize",
 "token_endpoint": "https://conf.uw.docomo.ne.jp/common/token",
 "userinfo_endpoint": "https://conf.uw.docomo.ne.jp/common/userinfo",
 "response_types_supported": [
  "code"
 ],
 "subject_types_supported": [
  "public"
 ],
 "id_token_signing_alg_values_supported": [
  "HS256"
 ],
 "scopes_supported": [
  "openid"
 ],
 "token_endpoint_auth_methods_supported": [
  "client_secret_basic"
 ],
 "grant_types_supported": [
  "authorization_code"
 ]
}
```

ここから issuer の文字列を取り出し、ファイル名を求めて、ファイル名を変更します。

```
$ cat d_accoutn_connect.json | jq .issuer | sed 's|^"||;s|"$||;s|^https://||;s|/$||'
conf.uw.docomo.ne.jp

$ echo -n 'conf.uw.docomo.ne.jp' | jq -s -R -r @uri
conf.uw.docomo.ne.jp

$ mv d_accoutn_connect.json conf.uw.docomo.ne.jp.provider
```

次に `conf.uw.docomo.ne.jp.client` としてクライアント ID とクライアントシークレットが入った json を用意します。以下のようにします。

```conf.uw.docomo.ne.jp.client
{
    "client_id":
    "xxxxxxxxxxxxxxxx",
    "client_secret":
    "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
```

最後に `conf.uw.docomo.ne.jp.conf` として OP 毎に変更する設定値を用意します。以下のようにしてみました。

```conf.uw.docomo.ne.jp.conf
{
    "scope": "openid",
    "response_type": "code"
}
```

以上 3 ファイル をメタデータディレクトリに入れ、オーナーとパーミッションを設定します。

```
$ sudo cp conf.uw.docomo.ne.jp.* /var/cache/apache2/mod_auth_openidc/metadata/
$ sudo chown www-data:www-data /var/cache/apache2/mod_auth_openidc/metadata/conf.uw.docomo.ne.jp.*
$ sudo chmod 600 /var/cache/apache2/mod_auth_openidc/metadata/conf.uw.docomo.ne.jp.*
```

## Apache サイト設定

`/etc/apache2/sites-available/example.com-ssl.conf` でインクルードしている mod\_auth\_openidc の設定ファイルを先ほど作成した設定ファイルへ変更します。クライアントシークレットなどの設定はメタデータの方で指定するため不要になるので削除します。

```
		Include mods-available/auth_openidc_google_account.conf
		Include mods-available/auth_openidc_client_secret_google.conf
```

を

```
		Include mods-available/auth_openidc_multiple_provider.conf
```

に変更します。ファイル全体としては以下のようになります。

```example.com-ssl.conf
<IfModule mod_ssl.c>
	<VirtualHost _default_:443>
		ServerAdmin webmaster@example.com

		DocumentRoot /var/www/example.com

		ErrorLog ${APACHE_LOG_DIR}/error.log
		CustomLog ${APACHE_LOG_DIR}/access.log combined

		SSLEngine on

		SSLCertificateFile /etc/ssl/certs/ssl-cert-test-example-com.pem
		SSLCertificateKeyFile /etc/ssl/private/ssl-cert-test-example-com.key

		SSLCertificateChainFile /etc/apache2/ssl.crt/test-intermediate-ca.pem

		Include mods-available/auth_openidc_multiple_provider.conf
		OIDCRedirectURI https://example.com/oidc/redirect_uri
		OIDCCryptoPassphrase foobarbaz
		OIDCCookiePath /oidc/
		OIDCDefaultLoggedOutURL https://example.com/loggedout.html

		<Location /oidc/>
			AuthType openid-connect
			Require valid-user

			Options +Includes
			DirectoryIndex index.shtml index.html
		</Location>
	</VirtualHost>
</IfModule>
```

### 設定の確認とサービス再起動

設定が正しいか確認します。

```
$ sudo apache2ctl configtest
Syntax OK
```

として上記のように Syntax OK と出たら確認 OK ですので、

```
$ sudo service apache2 restart
```

として再起動します。

## 動作確認

Windows のブラウザ（Edge など）で https://example.com を開き、「ログイン」をクリックします。「Select your OpenID Connect Identity Provider」という OP を選択する画面になり「conf.uw.docomo.ne.jp/」か「accounts.google.com」を選べるようになります。前者を選ぶと d アカウント・コネクトの認証画面に、後者を選ぶと Google アカウントの認証画面になって、どちらも認証されたら OIDC 動作確認用コンテンツ「OpenID Connect ログインしました」画面（ログイン中画面）になります。一旦「ログアウト」してトップに戻ってから再度「ログイン」すると、再度 OP 選択ができます。

やはりどちらを選んでも共通的に得られる情報は REMOTE\_USER だけなので、ユーザ別に処理を変えたい場合には REMOTE\_USER を見るのがよさそうです。例えば、特定のユーザだけに使わせたいような機能の場合は、あらかじめ何らかの方法で使用を許可する REMOTE\_USER のリストなどを持っておいて、それとリクエストのあった REMOTE\_USER の内容を照合するような方法ができそうです。

## OP 選択画面のカスタマイズ

ここまでで出てくる OP 選択画面「Select your OpenID Connect Identity Provider」は mod\_auth\_openidc のデフォルトのものですが、「d アカウント・コネクト」などの名称が出てきませんしユーザには少々不親切な感じがします。そこでこれをカスタマイズしてみます。

### CGI

カスタマイズするために [mod\_auth\_openidc の説明](https://github.com/zmartzone/mod_auth_openidc/wiki/Multiple-Providers#discovery) を読むと動的なパラメータ `x_csrf` を扱わなければならないことがわかります。つまり、選択画面を静的なページとして用意することができないということになります。パラメータによって動的にページを生成する方法はいくつかありますが、ここでは古くから使われている CGI を使ってみることにします。

#### CGI 有効化

まず、CGI が使えるように有効化（`/etc/apache2/mods-enabled` にシンボリックリンクを作る）します。

```
$ sudo a2enmod cgid
```

#### コンテンツ

とりあえず Ubuntu 20.04 には Python 3.8.10 が入っていたので、これで [適当に CGI を作ってみました](https://gist.github.com/trueroad/29974ea939e7b57a8539f0626cd8df6b)。この `op_selector.py` を https://example.com/oidc/cgi-bin/op_selector.cgi でアクセスできるようにします。まずはファイルを置きます。

```
$ git clone https://gist.github.com/29974ea939e7b57a8539f0626cd8df6b.git
$ cd 29974ea939e7b57a8539f0626cd8df6b
$ sudo mkdir -p /var/www/example.com/oidc/cgi-bin/
$ sudo cp op_selector.py /var/www/example.com/oidc/cgi-bin/op_selector.cgi
```

この CGI は最初の方の変数 `ISS_LIST` が、OP 選択用表示名と iss のタプル、を入れたリストになっています。とりあえず d アカウント・コネクトと Google アカウントの 2 択になるようにしてありますが、このリストをいじれば他の OP を追加したり削除したりすることができます。また、コールバックの URL がパラメータ `oidc_callback` で指定されなかった場合（普通はそんなことないハズですが）のデフォルトコールバック URL を変数 `DEFAULT_CALLBACK_URL` へ指定するようにしています。

### サイト設定

`/etc/apache2/sites-available/example.com-ssl.conf` に、 https://example.com/oidc/cgi-bin/ 以下で CGI が使えるようにする以下の設定を入れます。

```
		<Location /oidc/cgi-bin/>
			Options +ExecCGI
			AddHandler cgi-script .cgi
		</Location>
```

また、 https://example.com/oidc/cgi-bin/op_selector.cgi は /oidc/ 以下にあるので、そのままでは OIDC 認証が必要になってしまいます。しかし、OP 選択時にはまだ認証が終わっていない状態で表示できなければなりません。そこで、この URL だけ認証を必要としないようにする以下の設定を入れます。

```
		<Location /oidc/cgi-bin/op_selector.cgi>
			Require all granted
		</Location>
```

最後に、mod\_auth\_openidc に対して、デフォルトの OP 選択画面でなくて https://example.com/oidc/cgi-bin/op_selector.cgi を使うように指示する以下の設定を入れます。

```
		OIDCDiscoverURL https://example.com/oidc/cgi-bin/op_selector.cgi
```

ファイル全体は以下のようになります。

```example.com-ssl.conf
<IfModule mod_ssl.c>
	<VirtualHost _default_:443>
		ServerAdmin webmaster@example.com

		DocumentRoot /var/www/example.com

		ErrorLog ${APACHE_LOG_DIR}/error.log
		CustomLog ${APACHE_LOG_DIR}/access.log combined

		SSLEngine on

		SSLCertificateFile /etc/ssl/certs/ssl-cert-test-example-com.pem
		SSLCertificateKeyFile /etc/ssl/private/ssl-cert-test-example-com.key

		SSLCertificateChainFile /etc/apache2/ssl.crt/test-intermediate-ca.pem

		Include mods-available/auth_openidc_multiple_provider.conf
		OIDCRedirectURI https://example.com/oidc/redirect_uri
		OIDCCryptoPassphrase foobarbaz
		OIDCCookiePath /oidc/
		OIDCDefaultLoggedOutURL https://example.com/loggedout.html
		OIDCDiscoverURL https://example.com/oidc/cgi-bin/op_selector.cgi

		<Location /oidc/>
			AuthType openid-connect
			Require valid-user

			Options +Includes
			DirectoryIndex index.shtml index.html
		</Location>
		<Location /oidc/cgi-bin/>
			Options +ExecCGI
			AddHandler cgi-script .cgi
		</Location>
		<Location /oidc/cgi-bin/op_selector.cgi>
			Require all granted
		</Location>
	</VirtualHost>
</IfModule>
```

### 設定の確認とサービス再起動

再度設定の確認とサービス再起動をします。

```
$ sudo apache2ctl configtest
$ sudo service apache2 restart
```

### 動作確認

いったんログアウトしてから再度ログインしようとすると、カスタマイズされた OP 選択画面になり、「d アカウント・コネクト」をクリックすると d アカウント・コネクトで、「Google アカウント」をクリックすると Google アカウントで、それぞれ認証ができるようになりました。

## 次回

Web で OIDC 認証としては、とりあえずできるところまでできたかなと思っています。次はネイティブアプリと連携して REST API 的なサービスを提供するにはどうすればいいのか考えてみたいと思っています。