KeycloakをInternet Explorerに（それなりに）対応させた話

# はじめに
本日のテーマは「KeycloakをInternet Explorerに（それなりに）対応させた話」です。
微妙なテーマですが、意外と需要が有るような気もしています。
なお、Keycloak自体についてはあまり深く説明せず「ある程度は知ってる前提」で進めさせていただきます。

Keycloakは過去のNRIアドベントカレンダーでも何度も取り上げています。
特に2017年はKeycloak特集でしたので「Keycloakとはなんぞや？」という方はご覧ください。
* https://qiita.com/advent-calendar/2017/keycloak-by-openstandia ：★Keycloak特集
* https://qiita.com/advent-calendar/2018/nri-openstandia
* https://qiita.com/advent-calendar/2019/midpoint-by-openstandia
* https://qiita.com/advent-calendar/2020/nri-openstandia
* https://qiita.com/advent-calendar/2021/nri-openstandia

# 概要
Keyclaokはいわゆるモダンブラウザ（Chrome、Edge、Firefox、Opera、Safari など）での利用が想定されているようです。
「ようです」と曖昧な書き方をしているのは、公式ドキュメントからはブラウザのサポートに関する記述が見つからなかったためです。
ですので、IEがサポート外であるとは明言はされていませんが、Microsoftは2022年6月15日にIEのサポートを終了していますし、2023年2月14日以降はIEにアクセスできなくするとのアナウンスもしています。
よって、サポート外であっても仕方のない状況ではあります。

しかし、ビジネスとしてはそうも言ってはいられません。
IEでしか動かないシステムというものも世の中には存在します。
そうしたシステムのために、EdgeにはInternetExplorerモードという機能が用意されています。
https://learn.microsoft.com/ja-jp/deployedge/edge-ie-mode

IEモードは少なくとも2029年までサポートされることがMicrosoftから公式に発表されています。
IE必須のシステムもIEモードの利用を前提とすればあと7年ほどは稼働させることが可能です。
そういったところにKeycloakを導入するとなると、IE対応が（それなりに）必要となってくるという訳です。

実際に私が今年参画したのが、まさにIEでないと動かないシステムに対してKeycloakを導入するプロジェクトでした。
本記事はそこでの体験をベースにしています。


# 本題 -KeycloakをInternet Explorerに（それなりに）対応させた話-

## 要件など
* Keycloak-19.0.2 を使用。
* **連携先のシステムはすべてIEでしか動かない。**
* mod_auth_openidcを利用したリバプロ型構成とする。
    * 参考：[Keycloakでリバプロ型構成を組んでみる（mod_auth_openidc編）](https://qiita.com/yagiaoskywalker/items/44a0bc6034083477628f)
* 複数の連携先システムにシングルサインオンさせたい。シンプルにID/パスワードで認証する。
    * 多要素認証などは今後の課題として今回は対応しない。

前提として、連携先システム（クライアント）に「OpenID Connectの仕様に従って認証・認可する機能」が必要です。
当然ながらこの点はブラウザがIEであってもモダンブラウザであっても違いはありません。
連携先システムに機能が存在すれば、Keycloakはブラウザを問わず一通りの動作はしてくれました。

また、Keycloak管理コンソールはIE対応せずモダンブラウザでアクセスすることとしました。
Keycloakの最近のアップデートで追加された管理コンソール用の新UI（keyclaok.v2テーマ）はモダンブラウザでないと動作せず、IE対応は非常に困難であるためです。

## 問題と対策

### 1. 画面のレイアウトが崩れる問題
一通りの動作はするものの、Keycloakのデフォルト（keycloakテーマ）のログイン画面等をIEモードで表示するとスタイルシートがうまく適用されず、レイアウトが崩れてしまいました。

Edge：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332096/9c4be48a-12c3-6915-f498-09a6530f2b97.png)

IEモード：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332096/50d45de7-d353-9195-e446-54db819024d8.png)

#### 原因
次の画像はログイン画面でEdge（左）とIEモード（右）の開発者ツールを開いたところです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332096/7174a27e-1418-7a19-f669-c2eb39d56efb.png)
IEモードではスタイルのいくつかの値の下に「赤色の波線」が表示されています。
これがレイアウト崩れの原因です。
IEは最近のCSS仕様をサポートしておらず、特に[var()関数](https://developer.mozilla.org/ja/docs/Web/CSS/var)に対応していません。
そのため想定したスタイルが適用されずレイアウトが大きく崩れてしまいます。

#### 対策
CSSを補完しました。
コードはgithubにIE対応テーマとしてアップしてあります。
https://github.com/yaskojima/keycloak_campat_ie_theme
* [compat_ie.css](https://github.com/yaskojima/keycloak_campat_ie_theme/blob/main/login/resources/css/compat_ie.css)が補完用のCSSです。
* これをテーマが読み込むように[theme.properties](https://github.com/yaskojima/keycloak_campat_ie_theme/blob/main/login/theme.properties#L4)に設定します。

これを適用するとIEモードでもレイアウトが（それなりに）整います。

IEモード（IE対応テーマ）：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332096/52f50de4-a039-b2ff-5735-6ab80a3e1478.png)


##### 経緯
この対策法に至る経緯には若干の紆余曲折がありました。
Keycloakの画面（テーマ）には、[PatternFly 4](https://www.patternfly.org/v4/)が利用されています。
PatternFly 4ではCSSに大量のvar()関数が使用されていますが、前述の通りIEはvar()関数に対応していません。

ならば対応させれば良いのではと考えて、まず試してみたのが、[css-vars-ponyfill](https://jhildenbiddle.github.io/css-vars-ponyfill/)の導入です。
ponyfillとは、最近の機能をサポートしていない古いブラウザーで、その機能を使えるようにするためのコードを意味します。
*※厳密にはそれはpolyfillと呼び、それをポニーちゃん* 🐴 *のように安全にしたものをponyfillと呼ぶのだとか・・・？*
とにかく、css-vars-ponyfillを導入するとIEでもvar()関数を使えるようになります。

導入してみたところ、たしかに手軽に適用はできました。
しかし、レイアウトが崩れた画面が表示されてから一呼吸おいてキレイなレイアウトで再表示される、といった動きになってしまいました。
原因を調査してみると、PatternFly 4はKeycloakが利用していない部分も含めて非常に大量のvar()関数が使われていました。
画面表示のたびに、利用していない値も含めてそれら全てを再計算して再描画するという動きになっているため、無駄な計算処理が大量に走り、動作が重くなっていたのです。
これでは実用に耐えられないと判断し、css-vars-ponyfillの導入は断念し、地道にコツコツとCSSを修正する方針に転換しました。

具体的には、以下のように作業しました。
* モダンブラウザとIEモードでの表示を比較して、IEモードでレイアウトが崩れている場所を探します。
* IEモードの開発者ツールでその部分を確認し、スタイルにvar()関数が使われているために値が計算できていないところを見つけます。
* モダンブラウザでは、それがどんな値として計算されているのかを確認します。
* IEモード用のCSSにその要素と値を追加します。

この作業をコツコツと繰り返して、レイアウト崩れが無くなるまでCSSを修正していくことで、IEモードでもレイアウトを（それなりに）整えることができました。

### 2. またしても画面のレイアウトが崩れる問題
さて、上記の対策を施したcompat_ieテーマを適用して、連携先システムとの結合テストに入りました。
すると、いくつかの連携先システムへログインしようとすると、またしても画面のレイアウトが崩れています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332096/f585ed8e-3186-7289-1716-408d442745ed.png)

#### 原因
原因は「互換モード：IE5」が指定されているためでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332096/9f16f9a9-6ac2-ba6d-abe3-791bc37acb08.png)

互換モードとして指定できるのは以下のものです。
* IE11 ドキュメントモード
* IE10 ドキュメントモード
* IE9 ドキュメントモード
* IE8 ドキュメントモード
* IE7 ドキュメントモード
* IE5 ドキュメントモード
* IE8 エンタープライズモード　※IE11のIE8互換性が高いモード
* IE7 エンタープライズモード　※IE11のIE7互換性が高いモード

このうち、IE5の場合のみ画像のようにレイアウトが崩れました。
中央のエリアが横幅いっぱいに広がっていますので、レスポンシブデザインがうまく適用できていないと考えられます。
さすがにIE5は古すぎて、レスポンシブデザインには対応できないようです。

それではなぜ「互換モード：IE5」が選択されてしまったのでしょうか？
どうやら互換モードはバージョンを明示的に指定していない場合は、遷移前の画面で指定された互換モードを引き継ぐことになっているようです。
つまり、以下のような動きとなっていました。

1. 連携先システムの認証済でないとアクセスできないURLへアクセスする。
ここで対象のURLは「互換モード：IE5」で表示するように設定が仕込まれている。
1. 未認証なのでKeycloakのログイン画面へ遷移する。
画面遷移前に設定された「互換モード：IE5」が引き継がれて表示される。

#### 対策
対策は、Keycloakのログイン画面は「互換モード：IE11」で表示するように明示的に指定することです。
具体的には、template.ftlの[このコード](https://github.com/yaskojima/keycloak_campat_ie_theme/blob/main/login/template.ftl#L7)です。
```
<meta http-equiv="X-UA-Compatible" content="IE=11">
```
このメタタグを追記することで、ログイン画面は常に互換モード：IE11で表示できるようになりました。

### 3. ログイン認証後に「400 Bad Request」が発生する問題
さて、上記の対策を施してから、あらためて各連携先システムとの結合テストを実施したのですが、今度はログイン認証後に「400 Bad Request」が発生してしまいました。
それも、今までは問題なくログインできていたシステムでも発生してしまいます。

#### 原因
まずは各連携先システムと結合テストして「400 Bad Request」が発生する条件を確認することにしました。
すると連携先システムが「互換モード：IE11」で表示するように仕込まれていれば問題なくログインできますが、「互換モード：IE11」**以外**で表示するように仕込まれていると「400 Bad Request」が発生することがわかりました。
このことから、原因はまたもやも「互換モード」にあるように考えられます。

また事象の発生時の各種ログを調査したところ、`mod_auth_openidc`のエラーログに以下のような出力が見つかりました。
```
oidc_handle_authorization_response: invalid authorization response state and no default SSO URL is set, sending an error...
```
これらの手がかりから、原因は一連の認証処理の中で互換モードが変化することだとわかりました。

mod_auth_openidcは、認証中に状態（state）のフィンガープリントを計算しています。
一連の認証処理の間にフィンガープリントが変化したら、何らかの異常が起きたと判断され、認証処理は中断されます。
デフォルトでは「User-Agentヘッダー」および「X-Forwarded-Forヘッダー」の値をフィンガープリント計算のインプットとして使用します。
つまり、一連の認証処理の間にこれらの値が変化していたらフィンガープリントも変化してしまい、正常な認証処理とは判断されなくなってしまうのです。
今回の場合で言うと、認証処理中に互換モードが「IE11」**以外**から「IE11」に変わると「User-Agentヘッダー」の値が変化してしまうため異常な状態であると判断されてしまいました。

#### 対策
この問題を解決するために、`mod_auth_openidc`の[OIDCStateInputHeaders](https://github.com/zmartzone/mod_auth_openidc/blame/master/auth_openidc.conf#L969-L973)プロパティを使用しました。
このプロパティは、フィンガープリント計算のインプットに「User-Agentヘッダー」および「X-Forwarded-Forヘッダー」の値を使用するか否かを設定することができます。
今回は「User-Agentヘッダー」は使わず「X-Forwarded-Forヘッダー」のみを使用することにして、以下のように設定しました。
※計算のインプットを減らすということはセキュリティリスクを高めることです。リスクを認識したうえで自己責任で設定してください。
```
OIDCStateInputHeaders  x-forwarded-for
```
この設定を追加したところ、見事に「400 Bad Request」は発生しなくなりました。

ちなみに偶然にも、このプロパティはNRIの @wadahiro が開発したものでした。
助かりました！ ありがとうございます！

# 感想
以上の対策により、KeycloakをInternet Explorerに（それなりに）対応させることができました。

**こんな苦労をしたくなければ、シングルサインオンを導入する前に各システムのモダンブラウザ対応を優先することを強くお勧めします！！！**

# おまけ：Microsoft Edge の互換性
今回の対応の中でせっかく調べたので「Microsoft Edge の互換性」についも触れておきます。

Edgeのアドレスバーに
```
edge://compat/
```
と打ち込むと「Microsoft Edge の互換性」のページを開くことができます。

[2. またしても画面のレイアウトが崩れる問題 の 原因](#%E5%8E%9F%E5%9B%A0-1)の中で、
> ここで対象のURLは互換モード：IE5で表示するように設定が仕込まれている。

と書きましたが、その設定を仕込むことができます。

## Internet Explorer モード サイトの一覧
```
edge://compat/enterprise
```
ここに設定のXMLファイルを読み込むことで、Edgeで特定のサイト開く際のIEモードや互換モードを指定することができます。
設定ファイルの読み込み元は `http://...` も指定でき、プロキシの自動構成スクリプト（PACファイル）のように配信できるので企業での利用に向いています。

## エンタープライズ サイト リスト マネージャー
```
edge://compat/sitelistmanager
```
こちらは、前述の設定XMLファイルを作成するための補助ツールです。
あくまでもXMLを作成できるだけで、このページに入力した内容がEdgeに反映されるわけではない点に注意してください。
このページで作成したXMLファイルを、前述の「Internet Explorer モード サイトの一覧」ページで読み込むことでEdgeに反映されます。

## 既定のブラウザー
```
edge://settings/defaultBrowser
```
こちらのページの「Internet Explorer の互換性」でも、IEモードで開くサイトを設定することができます。
ただし設定は配信等で適用することはできず端末単位に手動で設定することになります。
しかも、設定はデフォルトで30日（ローカルグループポリシーで延長しても最大90日まで）で消えてしまいます。
長期的に使うのであれば定期的に手動で設定しなおす必要があります。
よって、企業で管理して全従業員に適用するような利用には向いていません。
開発者が一時的に試してみるような用途には使えます。

## 共有Cookie
そして「既定のブラウザー」の「Internet Explorer の互換性」からは設定できず、「Internet Explorer モード サイトの一覧」および「エンタープライズ サイト リスト マネージャー」からのみ設定できる重要なものが一つあります。
「共有Cookie」です。

EdgeのIEモードとは「モード」と言いつつも実際はEdgeの中にIEを立ち上げているようなものです。
つまりEdgeのCookieとIEモードのCookieは別モノとして管理されているです。

今回対応した案件では「連携先のシステムはすべてIEでしか動かない。」となっていました。
しかし近い将来、各システムにモダンブラウザ対応（Edge対応）をしていくことになるでしょう。
また、新たなシステムが構築されれば当然モダンブラウザでの動作を前提としていることでしょう。
それら新旧のすべてのシステムが一斉にモダンブラウザ対応できるのであれば良いのですが、一部のシステムはEdgeでの動作を想定するが、一部は引き続きIEでの動作を想定しているという過渡期がある可能性は高そうです。

すると、せっかくのシングルサインオンなのに「IE想定のシステムで一度ログインして、Edge想定のシステムでもう一度ログインする」という【ダブル】サインオンが必要になってしまいます。

これを解決できるであろう方法が「共有Cookie」です。
ここで設定を行うことで、EdgeとIEモードのCookieを共有させる設定が可能です。
Keycloakが使用している認証クッキーを設定することで【シングル】サインオンが実現できるはずです。
*※今回の対応では、関連システムはすべてIEでの動作を想定していましたので、実際に「共有cookie」を試せてはいません。
あしからず。*

# 参考リンク
https://learn.microsoft.com/ja-jp/deployedge/edge-ie-mode