KeycloakでOpenID Connectを使ってSSOを実行（認可コードフロー編）

本記事では、KeycloakでOIDCを使いシングルサインオンを実行してみようと思います。

認可にはいくつかの流れ（フロー）があり、今回はその中でも特にセキュアである認可コードフローでシングルサインオンを実現してみようと思います。

# 認可コードフローとは
認可コードフローとは、クライアントID・クライアントシークレットを安全に保存できるアプリ（コンフィデンシャルクライアント）に適したフローです。
ユーザを介さずクライアントとOP（認証基盤）間でのみアクセストークンの受け渡しをするため、アプリケーションユーザにアクセストークンが流出するリスクが低いことが他のフローと比較したときの大きな特徴です。

以下は具体的なシーケンス図となります。

<!-- %%{init:{"theme": "forest", "themeVariables": {"fontSize": "1px"}}}%% -->
```mermaid
sequenceDiagram
    actor User as アプリユーザ
    Participant RP as アプリ(Relying Party)
    participant OP as Keycloak(OpenID Provider)

    User ->> RP: ユーザがアプリにアクセス
    RP ->> User: Keycloakへリクエストするようにリダイレクト
    activate User
    User ->> OP: ユーザからKeycloakへリクエストを送信
    deactivate User
    Note left of OP: 認可エンドポイント
    OP --> User: 認証 & 同意

    OP ->> User: 認可コードを付与したリダイレクトURIを送信
    activate User
    User ->> RP: 認可コードをアプリに渡す
    deactivate User
    Note left of RP: リダイレクトURI
    RP ->> OP: 認可コードと引き換えで<br/>アクセストークンをもらうようリクエスト
    Note left of OP: トークンエンドポイント
    OP ->> RP: アクセストークンとIDトークンをレスポンス
    RP ->> RP: IDトークンを検証
    RP --> OP: ユーザ情報エンドポイントにアクセス＋<br/>ユーザ情報を取得（オプション）
    RP ->> User: ユーザに画面を返す
```

シーケンスを見ていただけるとわかる通り、トークンエンドポイントでアプリ側からKeycloakへリクエストを実施しており、アプリ側が保持しているクライアントIDとクライアントシークレットがBasic認証のヘッダー情報としてリクエストに含まれた状態となります。
リクエストにクライアントIDとクライアントシークレットを含める必要があることから、実際に運用する際はクライアント情報が外部に漏出する恐れが低いコンフィデンシャルクライアントで本フローを活用することがベストプラクティスと言われております。
# 構築手順
<!-- 今回のシナリオで登場する rp op lbを構築 -->
以下のような構成で構築を行う。
```mermaid
flowchart LR
    User[Browser] -->|http| RP[RelyingParty\nlocalhost:81]
    User -->|http| OP[Keycloak\nlocalhost:8080]
    RP <-->|http| OP
```

## OpenID Provider
タイトルにもある通り、OpenID ProviderとしてKeycloakを用います。
Keycloakの構築手順は[公式サイト](https://www.keycloak.org/guides#getting-started)でも紹介されております。
今回はOpenJDKを手元にインストールして実行してみます。
具体的な構築手順は以下の通りです。
1. OpenJDKをインストール
2. keycloakをダウンロード
3. keycloakを実行
4. 特権ユーザを設定
5. レルム作成
6. クライアント作成
7. テスト用ユーザ作成

### OpenJDKのインストール
OpenJDKをインストールします。
今回は構築手順にのっとりOpenJDK17をインストールしましょう。

```bash
$ sudo apt install openjdk-17-jdk-headless
$ java --version
openjdk 17.0.9 2023-10-17
```
無事にインストールできました。

### Keycloakのダウンロード
Keycloakを公式リポジトリからダウンロードします。
最新版をダウンロードしましょう。（本記事は2023年末の内容のためじゃっかんバージョンが前のものになっております）

```bash
wget https://github.com/keycloak/keycloak/releases/download/23.0.4/keycloak-23.0.4.zip
```

### Keycloakを実行
それでは、Keycloakを動かしてみましょう。

```bash
bash bin/kc.sh start-dev
2024-01-16 19:10:11,518 WARN  [io.quarkus.agroal.runtime.DataSources] (main) Datasource <default> enables XA but transaction recovery is not enabled. Please enable transaction recovery by setting quarkus.transaction-manager.enable-recovery=true, otherwise data may be lost if the application is terminated abruptly
2024-01-16 19:10:12,955 WARN  [org.infinispan.PERSISTENCE] (keycloak-cache-init) ISPN000554: jboss-marshalling is deprecated and planned for removal
2024-01-16 19:10:13,192 WARN  [org.infinispan.CONFIG] (keycloak-cache-init) ISPN000569: Unable to persist Infinispan internal caches as no global state enabled
```

### 特権ユーザを設定
Keycloakではデフォルトでmasterレルムが備わっております。
このレルムを編集するためには特権ユーザを用意してあげる必要があります。
今回はadminというユーザを用意してあげましょう。

### レルム作成
次に、レルムを作成しましょう。
左メニューのレルム選択画面で[Create realm]というボタンがあるのでクリックしましょう。
![レルム作成の様子1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584403/8da9e048-303c-56da-f009-21bc3cce348d.png)
以下の画面が出てくるので、[Realm name]でレルム名を入力します。今回は[myrealm]という名前にしておきます。
![レルム作成の様子2](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584403/16245319-f2d7-5f46-0233-3c4b329cc65c.png)
作成に成功すれば以下のような画面が出てきます。
![レルム作成の様子3](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584403/76a9077e-150d-b8eb-c36d-0ae286c05a19.png)

### クライアント作成
次にクライアントを作成しましょう。
左メニューで[Clients]をクリックしましょう。
次に[Create Client]ボタンをクリックします。
![クライアント作成1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584403/73656e16-74f5-364d-126d-87e954436f6c.png)

以下のように設定します。
|設定項目|設定する値|
|:-:|:-:|
|Client type|OpenID Connect|
|Client ID|apache24|
|Name|自由記述|
|Description|自由記述|
|Always display in UI|Off|
![クライアント作成2](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584403/5e74563b-3374-9cff-8dac-e1460a728ad6.png)

次に、以下のような画面が表示されます。
![クライアント作成3](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584403/735b633e-5f4f-ef5c-be50-89f4235c0565.png)
こちらは以下のように設定しましょう。
|設定項目|設定する値|
|:-:|:-:|
|Client authentication|On|
|Authorization|Off|
|Authentication flow|今回は`Standard flow`にチェックしておく|

Authentication flowのチェック項目にはいろいろありますが、OAuth2.0に照らし合わせると以下の通りです。

|チェック項目|OAuth2.0で相当するフロー|
|:-:|:-:|
|Standard flow|認可コードフロー|
|Implicit flow|インプリシットフロー|
|Direct access grants|リソースオーナーパスワードクレデンシャル|
|Service accounts roles|クライアントクレデンシャル|

後日の記事ではStandard flow以外の各フローを用いる予定なので、留意しておいてください。

最後に、ログイン時のURLに関する設定を行います。
![クライアント作成4](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584403/d560b8c9-e236-16d6-663c-cbb46e80d4f5.png)
認可コードフローとインプリシットフローではリダイレクトURIが必要になるので、`Valid redirect URIs`に適切なリダイレクトURIを設定しておきます。このとき、`http://localhost:81/private/callback`を指定します（こちらのURIはRPを設定するときに再登場します）。


### テスト用ユーザ作成
さて、最後の手順となります。
クライアントを作成したので、あとはクライアントでログインするときのユーザ情報を追加しましょう。

ユーザ情報は左メニューの[Users]を選択し、[Add User]ボタンをクリックします。
クリックしたのち、Usernameに適当な文字列を入力して[Create]ボタンを押下することでテストユーザが作成されます(簡単な手順なのでスクショは省略)。

## RelyingParty
Relying Partyを構築します。(以後はRPと略記)
RPを用意することで、外部IdPに認証機能を委託して、クライアント側がシングルサインオンを実現できるようになります。
今回RPとして利用するのは、Apacheのモジュールとして提供されている「mod_auth_openidc」です。
こちらのモジュールをwebサーバに組み込むことで、SSOのフローをクライアント側で実施できます。
以下は具体的な構築手順です。
1. Apache2のインストール
2. mod_auth_openidcのインストール
3. 設定ファイルの編集

### Apache2のインストール
Apache2はパッケージ管理ツールを使ってインストールしましょう。
```bash
$ sudo apt install apache2
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following packages were automatically installed and are no longer required:
  bridge-utils dns-root-data dnsmasq-base ubuntu-fan
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
  apache2-bin apache2-data apache2-utils bzip2 libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap liblua5.3-0
  mailcap mime-support ssl-cert
Suggested packages:
  apache2-doc apache2-suexec-pristine | apache2-suexec-custom www-browser bzip2-doc
The following NEW packages will be installed:
  apache2 apache2-bin apache2-data apache2-utils bzip2 libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap
  liblua5.3-0 mailcap mime-support ssl-cert
0 upgraded, 13 newly installed, 0 to remove and 2 not upgraded.
Need to get 2139 kB of archives.
After this operation, 8518 kB of additional disk space will be used.
Do you want to continue? [Y/n] Y
Get:1 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 libapr1 amd64 1.7.0-8ubuntu0.22.04.1 [108 kB]
...
```
インストール実行後、動作するか見てみましょう。
```bash
$ apache2 -v
Server version: Apache/2.4.52 (Ubuntu)
Server built:   2023-10-26T13:44:44
 
$ systemctl status apache2
● apache2.service - The Apache HTTP Server
     Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2023-12-22 16:17:56 JST; 3min 54s ago
       Docs: https://httpd.apache.org/docs/2.4/
   Main PID: 5160 (apache2)
      Tasks: 55 (limit: 9431)
     Memory: 21.1M
     CGroup: /system.slice/apache2.service
             ├─5160 /usr/sbin/apache2 -k start
             ├─5161 /usr/sbin/apache2 -k start
             └─5162 /usr/sbin/apache2 -k start
 
Dec 22 16:17:56 kan-pc systemd[1]: Starting The Apache HTTP Server...
Dec 22 16:17:56 kan-pc systemd[1]: Started The Apache HTTP Server.
```
動いているようなので、試しにブラウザでアクセスしてみましょう。
![Apache2の稼働をブラウザで確認](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584403/dbc1d83e-3d74-9a71-17db-ad8f3676db5e.png)

また、今回はRPを81番ポートに対応させるため、以下のようにポートの設定を変更します。
```xml
Listen 81

<IfModule ssl_module>
	Listen 443
</IfModule>

<IfModule mod_gnutls.c>
	Listen 443
</IfModule>
```

### mod_auth_openidcのインストール
さて、次はApache2でOpenID Connectを利用できるように設定を組みこみしましょう。

```bash
$ sudo apt install libapache2-mod-auth-openidc
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following packages were automatically installed and are no longer required:
  bridge-utils dns-root-data dnsmasq-base ubuntu-fan
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
  libcjose0 libhiredis0.14
The following NEW packages will be installed:
  libapache2-mod-auth-openidc libcjose0 libhiredis0.14
0 upgraded, 3 newly installed, 0 to remove and 2 not upgraded.
Need to get 241 kB of archives.
After this operation, 773 kB of additional disk space will be used.
Do you want to continue? [Y/n] Y
Get:1 http://archive.ubuntu.com/ubuntu jammy/universe amd64 libhiredis0.14 amd64 0.14.1-2 [32.8 kB]
Get:2 http://archive.ubuntu.com/ubuntu jammy-updates/universe amd64 libcjose0 amd64 0.6.1+dfsg1-3ubuntu1.1 [36.9 kB]
Get:3 http://archive.ubuntu.com/ubuntu jammy/universe amd64 libapache2-mod-auth-openidc amd64 2.4.11-1 [171 kB]
Fetched 241 kB in 12s (19.8 kB/s)
Selecting previously unselected package libhiredis0.14:amd64.
(Reading database ... 27620 files and directories currently installed.)
Preparing to unpack .../libhiredis0.14_0.14.1-2_amd64.deb ...
Unpacking libhiredis0.14:amd64 (0.14.1-2) ...
Selecting previously unselected package libcjose0.
Preparing to unpack .../libcjose0_0.6.1+dfsg1-3ubuntu1.1_amd64.deb ...
Unpacking libcjose0 (0.6.1+dfsg1-3ubuntu1.1) ...
Selecting previously unselected package libapache2-mod-auth-openidc.
Preparing to unpack .../libapache2-mod-auth-openidc_2.4.11-1_amd64.deb ...
Unpacking libapache2-mod-auth-openidc (2.4.11-1) ...
Setting up libcjose0 (0.6.1+dfsg1-3ubuntu1.1) ...
Setting up libhiredis0.14:amd64 (0.14.1-2) ...
Setting up libapache2-mod-auth-openidc (2.4.11-1) ...
apache2_invoke: Enable module auth_openidc
Processing triggers for libc-bin (2.35-0ubuntu3.5) ...
```
インストールを実行しました。
### 設定ファイルの編集
次は、設定ファイルを編集しましょう。
今回は以下のように編集します。

```
OIDCProviderMetadataURL       http://localhost:8080/realms/myrealm/.well-known/openid-configuration
OIDCClientID                  apache24
OIDCClientSecret              xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OIDCResponseType              code
OIDCScope                     "openid"
OIDCSSLValidateServer         Off
OIDCProviderTokenEndpointAuth client_secret_basic

OIDCRedirectURI               http://localhost:81/private/callback
OIDCCryptoPassphrase          passphrase
OIDCPreservePost              On

<Location /private>
   AuthType         openid-connect
   Require          valid-user
</Location>
```

変数に関しては以下のようになっております。
|変数|概要|
|:-:|:-:|
|OIDCProviderMetadataURL       |OpenID Connectに関するメタデータ|
|OIDCClientID                  |クライアントID(Keycloakで設定した値を入力)|
|OIDCClientSecret              |クライアントシークレット|
|OIDCResponseType              |認証フローのタイプ(今回は認可コードフローなのでcode)|
|OIDCScope                     |スコープ|
|OIDCSSLValidateServer         |SSLの有効化を設定|
|OIDCProviderTokenEndpointAuth |トークンエンドポイントとの通信時の認証タイプ|
|OIDCRedirectURI               |リダイレクトURI|
|OIDCCryptoPassphrase          |cookieやセッション情報を暗号化するために用いるパスフレーズ|
|OIDCPreservePost              |POST実行時にリクエストパラメータを保存するか|

以上の設定を用いて接続ができるか確認してみましょう。
今回はPHPを用いて疎通確認用のechoサーバを構築しましょう。

## echoサーバ
以下のようにphpをモジュールとしてインストールします。
```bash
$ sudo apt install libapache2-mod-php
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following packages were automatically installed and are no longer required:
  bridge-utils dns-root-data dnsmasq-base ubuntu-fan
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
  libapache2-mod-php8.1
Suggested packages:
  php-pear
The following NEW packages will be installed:
  libapache2-mod-php libapache2-mod-php8.1
0 upgraded, 2 newly installed, 0 to remove and 2 not upgraded.
Need to get 1769 kB of archives.
After this operation, 5422 kB of additional disk space will be used.
Do you want to continue? [Y/n] Y
Get:1 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 libapache2-mod-php8.1 amd64 8.1.2-1ubuntu2.14 [1766 kB]
Get:2 http://archive.ubuntu.com/ubuntu jammy/main amd64 libapache2-mod-php all 2:8.1+92ubuntu1 [2898 B]
Fetched 1769 kB in 25s (71.4 kB/s)
Selecting previously unselected package libapache2-mod-php8.1.
(Reading database ... 27910 files and directories currently installed.)
Preparing to unpack .../libapache2-mod-php8.1_8.1.2-1ubuntu2.14_amd64.deb ...
Unpacking libapache2-mod-php8.1 (8.1.2-1ubuntu2.14) ...
Selecting previously unselected package libapache2-mod-php.
Preparing to unpack .../libapache2-mod-php_2%3a8.1+92ubuntu1_all.deb ...
Unpacking libapache2-mod-php (2:8.1+92ubuntu1) ...
Setting up libapache2-mod-php8.1 (8.1.2-1ubuntu2.14) ...
 
Creating config file /etc/php/8.1/apache2/php.ini with new version
Module mpm_event disabled.
Enabling module mpm_prefork.
apache2_switch_mpm Switch to prefork
apache2_invoke: Enable module php8.1
Setting up libapache2-mod-php (2:8.1+92ubuntu1) ...
Processing triggers for libapache2-mod-php8.1 (8.1.2-1ubuntu2.14) ...
```
次に、phpをapache2で利用できるよう設定を変更します。

```bash
sudo a2enmod php8.1
sudo systemctl reload apache2.service
```

次に、ファイルを編集しましょう。
```php:/var/www/html/private/index.php
<?php
phpinfo();
?>
```
それでは、通信を実施します。
ブラウザで以下のようにURLを入力します。
http://localhost:81/private/
すると、認可コードフローに従いKeycloakのログイン画面へとリダイレクトします。
![認可エンドポイント](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584403/1d5b03f5-43e9-4805-5341-7f95745016c2.png)
ユーザ名とパスワードに先ほどKeycloakの管理コンソール上で設定した値を入力します。
すると、以下のようにPHPによるリクエスト表示画面が出力されます。
![認証成功1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584403/6148912b-d8e1-0be9-edc1-3a4414cb65eb.png)
![認証成功2](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584403/07263063-0184-2893-32d8-c4a3d07621d9.png)
認可コードフローが動いていることを確認できましたね。


しかし、localhostとポート番号を直打ちで通信するのは少しイケていません。
そこで、今回はロードバランサーを用いてもっとよさげなアーキテクチャにしてしまいましょう。

## LoadBalancer
ロードバランサーを用いて処理を実現します。
近年、ユーザ数の増加に応じてサーバ側の負荷が増大することから、サーバ側を冗長化できるようにクライアントサーバ間にロードバランサーを挟むことが多くなってきました。
今回はそのアーキテクチャを利用してみましょう。

```mermaid
flowchart LR
    User[Browser] -->|https| LB[LoadBalancer\nlocalhost:80\nlocalhost:443]
    LB -->|http| RP[RelyingParty\nlocalhost:81]
    LB -->|http| OP[Keycloak\nlocalhost:8080]
```
## Keycloakの設定
今回は通信の中継地点としてロードバランサーをはさんでいるため、Keycloak側でHTTPヘッダー内のX-Forwarded-*情報を参照する必要があります。

そこで、以下のように実行オプションを追加します。

```bash
bash bin/kc.sh start-dev --proxy edge
```
`--proxy edge`オプションを指定することで、プロキシ（今回はロードバランサー）とKeycloak間でHTTPを介した通信を可能になります。

※（2024年4月追記）最新バージョンのKeycloak24.xでは`--proxy`オプションが非推奨になったので、以後は以下のように設定をお願いします。
```bash
bash bin/kc.sh start-dev --proxy-headers forwarded|xforwarded --http-enabled true
```
今回のアーキテクチャでは、ロードバランサーとKeycloakは同一のネットワークに存在しているので、このオプションで実行しましょう。

### Nginxの設定
今回はNginxを用いてロードバランサーを実現します。
以下は構築手順となります。
1. Nginxをインストール
2. SSL証明書を作成
3. Nginxの設定ファイルを編集

#### Nginxのインストール
Nginxをインストールします。
```bash
sudo apt install nginx
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
```

#### SSL証明書を作成
OpenSSLを用いてSSL証明書を作成しましょう。
詳細な作り方に関しては、既存のサイトがあるのでここでは省略します。
最終的なアウトプットとして、以下のファイルがあることを想定する。

|生成物|概要|
|:-:|:-:|
|server.crt|証明書|
|server.key|サーバの秘密鍵|

#### Nginxの設定ファイルを編集
今回は、ドメインを設定してそれらしく通信してみようと思います。
以下のようなドメインに設定しました。

|ドメイン|機能|紐づいたアドレス|
|:-:|:-:|:-:|
|sso.kanta-k.com|Keycloak|localhost:8080|
|app.kanta-k.com|RelyingParty|localhost:81|


X-Forwarded-forを処理できるよう、Keycloakとmod_auth_openidcの双方で設定を追加しましょう。
Keycloakについては、立ち上げるタイミングで「--proxy edge」オプションを追加して実行しているので、プロキシサーバから受け取ったHTTPヘッダー情報を処理する準備は整っています。
次に、mod_auth_openidcについては、`OIDCXForwardedHeaders`のディレクティブを追加します。
このディレクティブはmod_auth_openidcにHTTPヘッダーに含まれているX-Forwardedの情報を解釈する命令を出すためのものです。

```conf
OIDCXForwardedHeaders         X-Forwarded-Host X-Forwarded-Proto X-Forwarded-Port
```

また、指定URIが変更されたので、設定ファイルを書き換える必要があります。
そのため、以下のように書き換えます。

```
- OIDCProviderMetadataURL       http://localhost:8080/realms/myrealm/.well-known/openid-configuration
+ OIDCProviderMetadataURL       https://sso.kanta-k.com/realms/myrealm/.well-known/openid-configuration
  OIDCClientID                  apache24
  OIDCClientSecret              G7PRQbaldo7l17AN4Ls0fDfj2jcM9oVN
  OIDCResponseType              code
  OIDCScope                     "openid"
  OIDCSSLValidateServer         Off
  OIDCProviderTokenEndpointAuth client_secret_basic
  
- OIDCRedirectURI               http://localhost:81/private/callback
+ OIDCRedirectURI               https://app.kanta-k.com/private/callback
  OIDCCryptoPassphrase          passphrase
  OIDCPreservePost              On
+ OIDCXForwardedHeaders         X-Forwarded-Host X-Forwarded-Proto X-Forwarded-Port
  
  <Location /private>
     AuthType         openid-connect
     Require          valid-user
  </Location>
  <Location /public>
     OIDCUnAuthAction pass
     AuthType         openid-connect
     Require          valid-user
  </Location>
```

以上の設定を稼働しているサーバに反映するために、nginxとapacheの双方を再起動しましょう。

```bash
sudo apache2ctl test
sudo systemctl restart apache2.service
sudo nginx -t
sudo systemctl restart nginx.service
```

実行内容を確認します。
試しに、ディベロッパーツールをオンにしてネットワークを選択した状態で以下のアドレスに接続してみましょう。
> https://app.kanta-k.com/private/

すると、以下のように認証画面へ遷移します。
![ロードバランサー導入後のアプリアクセス時の様子](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584403/1c224e56-e5f6-d1f1-b668-58153695c13b.png)

画面下部にあるリクエストの様子を見てみましょう。
まず、アプリにアクセスを実行すると302のリダイレクトが実行されます。
この時の遷移先は `https://sso.kanta-k.com/`となっており、無事リダイレクトが実施されたことが確認できました。

そして、設定したユーザ名とパスワードを入力してログインを実施すると、以下のようにサインイン後の画面になります。

![ロードバランサー導入後のアプリアクセス時の様子](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584403/10e5ea0d-eb82-2aff-0faa-b336b6ffa408.png)

リクエストの様子を見てみると、sso.kanta-k.com からのレスポンスとして、リダイレクトURIが払い出されています。
次にそのURIへ認可コード付きでアクセスしており、アクセストークンを取得してから元々のアクセス先へ遷移しています。

このように、ロードバランサーを用いてドメイン付きでSSL通信を実現できることが確認できました。

# リクエストの確認
それでは、次は認可コードフローでは具体的にどのような通信のやり取りをしているのか、ブラウザを用いて確認してみましょう。

## ユーザがアプリにアクセス
### リクエスト
```
GET /private/ HTTP/1.1
Host: app.kanta-k.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
```
上記のリクエストは、SSOが実行されるパスにアクセスを実施している。

### レスポンス
```
HTTP/1.1 302 Found
Server: nginx/1.18.0 (Ubuntu)
Date: Mon, 22 Jan 2024 08:42:17 GMT
Content-Type: text/html; charset=iso-8859-1
Content-Length: 551
Connection: keep-alive
Set-Cookie: mod_auth_openidc_state_8sGyV5I83IZXfq54qIZWiUBFUNA=Cm1DiY4XMQULIc3L.KpFUzOY5m21RD3yyjsJwy6445Uwd3...; Path=/; Secure; HttpOnly; SameSite=None
Cache-Control: no-cache, no-store, max-age=0
Location: https://sso.kanta-k.com/realms/myrealm/protocol/openid-connect/auth?response_type=code&scope=openid&client_id=apache24&state=8sGyV5I83IZXfq54qIZWiUBFUNA&redirect_uri=https%3A%2F%2Fapp.kanta-k.com%2Fprivate%2Fcallback&nonce=ki2a6ORxnj3o5u7Pw3gci7zBolnqIgifFuuWAmyhoXU
```
レスポンスは上記の通りになった。
通信の中身を見てみると、Cookieの設定が行われており、mod_auth_openidc_state_xxxというCookieは認証リクエストと認証レスポンスを紐づけるために用いられるものです。
（具体的な説明は[こちら](https://github.com/OpenIDC/mod_auth_openidc/wiki/Cookies#user-content-state-cookie)を参照）

そして、リダイレクト先としてKeycloakの認証画面が指定されています。
> Location: https://sso.kanta-k.com/realms/myrealm/protocol/openid-connect/auth?response_type=code&scope=openid&client_id=apache24&state=8sGyV5I83IZXfq54qIZWiUBFUNA&redirect_uri=https%3A%2F%2Fapp.kanta-k.com%2Fprivate%2Fcallback&nonce=ki2a6ORxnj3o5u7Pw3gci7zBolnqIgifFuuWAmyhoXU

フローの説明時にあったリダイレクトURIも`redirect_uri`変数として含まれていることがわかると思います。

## ユーザからKeycloakへリクエストを送信
### リクエスト
```
GET /realms/myrealm/protocol/openid-connect/auth?response_type=code&scope=openid&client_id=apache24&state=8sGyV5I83IZXfq54qIZWiUBFUNA&redirect_uri=https%3A%2F%2Fapp.kanta-k.com%2Fprivate%2Fcallback&nonce=ki2a6ORxnj3o5u7Pw3gci7zBolnqIgifFuuWAmyhoXU HTTP/1.1
Host: sso.kanta-k.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
```
上記のリクエストでは、受けとった302リダイレクトを実施しています。

### レスポンス
```
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Mon, 22 Jan 2024 08:42:17 GMT
Content-Type: text/html;charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: AUTH_SESSION_ID=df064d01-41ec-41b6-a88d-c832822f2f7a; Version=1; Path=/realms/myrealm/; SameSite=None; Secure; HttpOnly
AUTH_SESSION_ID_LEGACY=df064d01-41ec-41b6-a88d-c832822f2f7a; Version=1; Path=/realms/myrealm/; HttpOnly
KC_RESTART=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2ZTVlM2FmNC05YWE1LTQzZTctYWI1Zi0zZTdmMGQ4MWZmZTYifQ.e...; Version=1; Path=/realms/myrealm/; HttpOnly
Cache-Control: no-store, must-revalidate, max-age=0
Content-Language: en
Content-Security-Policy: frame-src 'self'; frame-ancestors 'self'; object-src 'none';
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Robots-Tag: none
X-XSS-Protection: 1; mode=block
Content-Encoding: gzip
```
レスポンスとして、以下のCookieが設定された認証画面が返ってきます。
- AUTH_SESSION_ID：認証時のセッションIDで、いわゆるスティッキーセッション（今回はロードバランサーで機能を実現したため）。
- KC_RESTART：クライアントがタイムアウトした際に認証セッションを再開するために利用されるCookie情報。


## 認可コードを付与したリダイレクトURIを取得
### リクエスト（認証&同意）
```
POST /realms/myrealm/login-actions/authenticate?session_code=8OcfqiYMxNIyYCyjkqf_MVnZ71Z0AMxDpopQCVGuAYI&execution=69a2dcda-6e81-4ee3-8feb-8c7a2d28ab07&client_id=apache24&tab_id=WElMWV4X040 HTTP/1.1
Host: sso.kanta-k.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 44
Origin: null
Connection: keep-alive
Cookie: AUTH_SESSION_ID=d4e4fde9-6067-43ba-ba93-e3c450187677; AUTH_SESSION_ID_LEGACY=d4e4fde9-6067-43ba-ba93-e3c450187677; KC_RESTART=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2ZTVlM2FmNC05YWE1LTQzZTctYWI1Zi0zZTdmMGQ4MWZmZTYifQ.e...
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
```
### レスポンス（リダイレクトURIを取得）
```
HTTP/1.1 302 Found
Server: nginx/1.18.0 (Ubuntu)
Date: Tue, 23 Jan 2024 07:04:38 GMT
Content-Length: 0
Connection: keep-alive
Cache-Control: no-store, must-revalidate, max-age=0
Set-Cookie: KEYCLOAK_LOCALE=; Version=1; Comment=Expiring cookie; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/realms/myrealm/; HttpOnly
KC_RESTART=; Version=1; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/realms/myrealm/; HttpOnly
KC_AUTH_STATE=; Version=1; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/realms/myrealm/
KEYCLOAK_IDENTITY=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2ZTVlM2FmNC05YWE1LTQzZTctYWI1Zi0zZTdmMGQ4MWZmZTYifQ.e...; Version=1; Path=/realms/myrealm/; SameSite=None; Secure; HttpOnly
KEYCLOAK_IDENTITY_LEGACY=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2ZTVlM2FmNC05YWE1LTQzZTctYWI1Zi0zZTdmMGQ4MWZmZTYifQ.e...; Version=1; Path=/realms/myrealm/; HttpOnly
KEYCLOAK_SESSION=myrealm/5b5ae651-8456-41d0-b222-da9d817f9ad7/d4e4fde9-6067-43ba-ba93-e3c450187677; Version=1; Expires=Tue, 23-Jan-2024 17:04:38 GMT; Max-Age=36000; Path=/realms/myrealm/; SameSite=None; Secure
KEYCLOAK_SESSION_LEGACY=myrealm/5b5ae651-8456-41d0-b222-da9d817f9ad7/d4e4fde9-6067-43ba-ba93-e3c450187677; Version=1; Expires=Tue, 23-Jan-2024 17:04:38 GMT; Max-Age=36000; Path=/realms/myrealm/
KEYCLOAK_REMEMBER_ME=; Version=1; Comment=Expiring cookie; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/realms/myrealm/; HttpOnly
Content-Security-Policy: frame-src 'self'; frame-ancestors 'self'; object-src 'none';
Location: https://app.kanta-k.com/private/callback?state=MrTxfdlMiYuS5Y_MAwB0NyMlBdU&session_state=d4e4fde9-6067-43ba-ba93-e3c450187677&iss=https%3A%2F%2Fsso.kanta-k.com%2Frealms%2Fmyrealm&code=8dc2c104-8eca-41cd-916c-2d36bee01b13.d4e4fde9-6067-43ba-ba93-e3c450187677.5db7d057-ea84-4b9d-a668-cfe671a18fd9
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Robots-Tag: none
X-XSS-Protection: 1; mode=block
```

302のリダイレクト先のURIを確認してみると、先ほど設定したリダイレクトURIになっていることが確認できます。また、認可コードがcodeとしてURI内で払出されていることも併せて確認できました。

## 認可コードをアプリに渡す
### リクエスト
```
GET /private/callback?state=MrTxfdlMiYuS5Y_MAwB0NyMlBdU&session_state=d4e4fde9-6067-43ba-ba93-e3c450187677&iss=https%3A%2F%2Fsso.kanta-k.com%2Frealms%2Fmyrealm&code=8dc2c104-8eca-41cd-916c-2d36bee01b13.d4e4fde9-6067-43ba-ba93-e3c450187677.5db7d057-ea84-4b9d-a668-cfe671a18fd9 HTTP/1.1
Host: app.kanta-k.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: mod_auth_openidc_state_MrTxfdlMiYuS5Y_MAwB0NyMlBdU=IU1kj3YUbCqTn5QQ.xK3GpM_bc0DiM8MiEXr58g7UovNjj2lWp-jeDv5LPtbYvysdxJF1h9Ta374LMgeuOPoY11XdjadzXLqswZLcDkKfCHN4QE8cKoPVgsVfhkuTNyrYFGoi88L5jTeg6Zyml-XO0Ltlis1WgjarH0Yp3KdY0k3f0xZLUq2YMDAWOB0TNOqgj5WT8Gdwk9-AWzma-D5wrqDr8KAh3PEVYPLggcOMfvRk0Tvz4YG48mCI4SOLIo2aRFVPhJOsgtWPsdW1c17YjHFxivBowfLQwAKFdQm7A3TiaXB8tMmlM4Y1gxsHRYulpKQL0LwiFvGQrImc2e48YOo7soaE_uK22u-pb2owSLjetgGAUcYiiKyvpPn-tl1BSLPZyoZDLeYMdJJsNo6_H98tZ9rBfTGAVEw91X8.U5C7HT5SH0TFI3i3hI_CRw
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
```

リクエスト後はフローに記載の処理がRPとOPで行われます。
- トークンエンドポイントからアクセストークンが払い出される
- RPでIDトークンを検証
- (ユーザ情報エンドポイントにアクセスしてユーザ情報を取得)

### レスポンス
```
HTTP/1.1 302 Found
Server: nginx/1.18.0 (Ubuntu)
Date: Tue, 23 Jan 2024 07:04:39 GMT
Content-Type: text/html; charset=iso-8859-1
Content-Length: 301
Connection: keep-alive
Set-Cookie: mod_auth_openidc_state_MrTxfdlMiYuS5Y_MAwB0NyMlBdU=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; HttpOnly; SameSite=None
mod_auth_openidc_session=5a7994f4-b895-4a2f-90d3-c40b971ec284; Path=/; Secure; HttpOnly; SameSite=None
Location: https://app.kanta-k.com:443/private/
```
Cookie上でセッション情報が払い出された状態としてレスポンスが返ってきます。
リダイレクト先は最初期にアクセスしていたURLです。

## フローの初期にアクセスしていたURIへアクセス
### リクエスト
```
GET /private/ HTTP/1.1
Host: app.kanta-k.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: mod_auth_openidc_session=5a7994f4-b895-4a2f-90d3-c40b971ec284
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
```
### レスポンス
```
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Tue, 23 Jan 2024 07:04:39 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 24903
Connection: keep-alive
Vary: Accept-Encoding
Content-Encoding: gzip
```

無事にアクセスできたことを確認できました。