Apache HTTP Server + mod_auth_openidc で OpenID Connect (OIDC) 認証が必要な Web サイトを作ってみます。
本記事は「1. 準備編」「2. d アカウント・コネクト編」「3. Google アカウント編」に続く「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
に置きます。
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 を用意します。以下のようにします。
{
"client_id":
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
"client_secret":
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
最後に accounts.google.com.conf
として OP 毎に変更する設定値を用意します。以下のようにしてみました。
{
"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 アカウント・コネクト編 の設定値、そして手動で ID トークンを取得してみた時の挙動などから適当に以下のように作ってみました。
{
"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 を用意します。以下のようにします。
{
"client_id":
"xxxxxxxxxxxxxxxx",
"client_secret":
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
最後に conf.uw.docomo.ne.jp.conf
として OP 毎に変更する設定値を用意します。以下のようにしてみました。
{
"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
に変更します。ファイル全体としては以下のようになります。
<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 の説明 を読むと動的なパラメータ x_csrf
を扱わなければならないことがわかります。つまり、選択画面を静的なページとして用意することができないということになります。パラメータによって動的にページを生成する方法はいくつかありますが、ここでは古くから使われている CGI を使ってみることにします。
CGI 有効化
まず、CGI が使えるように有効化(/etc/apache2/mods-enabled
にシンボリックリンクを作る)します。
$ sudo a2enmod cgid
コンテンツ
とりあえず Ubuntu 20.04 には Python 3.8.10 が入っていたので、これで 適当に CGI を作ってみました。この 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
ファイル全体は以下のようになります。
<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 的なサービスを提供するにはどうすればいいのか考えてみたいと思っています。