Keycloak で Opent ID Connect 認可コードフローをやってみる

ミライトデザインアドベントカレンダー２日目です。
[ミライトデザイン Advent Calendar 2021のカレンダー | Advent Calendar 2021 - Qiita](https://qiita.com/advent-calendar/2021/miraito-inc)

1日目は [ほげさん](https://zenn.dev/suzuki_hoge/articles/2021-12-intellij-background-image-35e11efe3b7ece) でした。
自分も IntelliJ をずっと使っていますが背景画像が設定できるのは知りませんでした。ありがとうございました。

2日目は最近触っている Keycloak で OIDC の認可コードフローを試してみたいと思います。

## この記事でやること

Keycloak で OIDC の認可コードフローを試してみます。

Laravel のコードが実際どんなことをしているのかにも触れていこうと思います。

OIDC の内容については触れていません。その代わりに自分が OIDC を勉強する際に参考にさせてもらった参考書や記事を貼っておきます。

https://qiita.com/TakahikoKawasaki/items/498ca08bbfcc341691fe

https://qiita.com/TakahikoKawasaki/items/4ee9b55db9f7ef352b47

[OpenID Connect入門: 概念からセキュリティまで体系的に押さえる | 土岐 孝平 | Kindle本 | Kindleストア | Amazon](https://www.amazon.co.jp/OpenID-Connect%E5%85%A5%E9%96%80-%E6%A6%82%E5%BF%B5%E3%81%8B%E3%82%89%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%81%BE%E3%81%A7%E4%BD%93%E7%B3%BB%E7%9A%84%E3%81%AB%E6%8A%BC%E3%81%95%E3%81%88%E3%82%8B-%E5%9C%9F%E5%B2%90-%E5%AD%9D%E5%B9%B3-ebook/dp/B08SCCDLJH)

[雰囲気で使わずきちんと理解する！整理してOAuth2.0を使うためのチュートリアルガイド (技術の泉シリーズ（NextPublishing）) | Auth屋 | 工学 | Kindleストア | Amazon](https://www.amazon.co.jp/%E9%9B%B0%E5%9B%B2%E6%B0%97%E3%81%A7%E4%BD%BF%E3%82%8F%E3%81%9A%E3%81%8D%E3%81%A1%E3%82%93%E3%81%A8%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B%EF%BC%81%E6%95%B4%E7%90%86%E3%81%97%E3%81%A6OAuth2-0%E3%82%92%E4%BD%BF%E3%81%86%E3%81%9F%E3%82%81%E3%81%AE%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB%E3%82%AC%E3%82%A4%E3%83%89-%E6%8A%80%E8%A1%93%E3%81%AE%E6%B3%89%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA%EF%BC%88NextPublishing%EF%BC%89-Auth%E5%B1%8B-ebook/dp/B07XT8H2YG/ref=pd_sbs_2/355-1067343-5559902?pd_rd_w=eyRUL&pf_rd_p=4e34a507-1281-42ae-953a-93a761caa89c&pf_rd_r=K11592KFJJVDRBVBVGS5&pd_rd_r=9d050c9b-4e77-45e3-9ae6-1957c6316332&pd_rd_wg=rnuQa&pd_rd_i=B07XT8H2YG&psc=1)



## 実際に動かしてみたい人は
### 環境構築

1. github からコードを pull してくる
[GitHub - kakiuchi-miraito/keycloak-oidc](https://github.com/kakiuchi-miraito/keycloak-oidc)
2. `make up`
3. `http://localhost:8082/auth/`

Keycloak の画面が表示されていれば ok です。

![FD088083-E55C-40E9-9F2F-12E39CED4456.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/dde63bb0-5272-b51e-e28e-c8e00bc24606.png)

次に Keycloak の管理画面から認証を行うユーザーと、クライアント（今回だとLaravel）を追加します。

- Administration Console にアクセス
    - id / password : admin / admin

### クライアントを追加

- Clients > Create

![D9A5102B-1552-45AD-A9E4-50D67A3C8719.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/c2f2c7cc-50fc-3b4c-82e0-09d6ecab31ba.png)


![92A1E0DF-50F3-450D-B8FF-B8FE2772EE80.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/4815c15a-d2a2-9988-4eed-7671cfe5bef6.png)


- Consent Required
    - 同意画面を表示する設定
- Access Type を confidential へ変更

- Valid Redirect URIs に `http://localhost*`を入力


![スクリーンショット 2021-11-16 2.23.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/50d9d325-128a-c065-9551-6a67b2ae17a6.png)


- Credentials の Secret をコピー（後で使う）

![98D042A2-89AC-4399-A20B-F9E0AEFC33E9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/a22cc5e9-e266-6642-f1c0-7d17cee2f87d.png)

### User を追加

- Users > Add user

![BAEBF410-1EC7-48A2-A17A-A2781C0992E9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/64e45d32-a8d2-7462-ca1b-c9bab0577300.png)


- Username を入力（なんでも ok）

![3DF547F9-546C-4AC0-9AEB-3D6C4AC76092.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/06141fd9-114c-a943-fa65-cbced5ae4eaa.png)


- Credentials から Password を設定
    - Temporary は off
        - ユーザーがログイン後にパスワードを変更する必要があるかどうかの設定。今回はどうでもいいので off
        
![EA5849DD-F3BF-4738-8B39-0BA35B0311C5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/d735dc22-7d2f-c08f-aba9-54392c34ae4e.png)

## 認可コードフローを試してみる

![OIDC - 基本的なシーケンス図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/031b1727-a837-c1ed-f63b-d637e4f0ed93.png)


### 認可コードフローで ID token を取得

- `backend/app/Http/Controllers/TokenRequest.php`の`client_id` と`client_secret` を作成したクライアントの値に修正
- `http://localhost/getAuthorizationCode` へアクセス
    - Keycloak のログイン画面が表示される

![2B3DFA46-FEF3-4719-82D7-1716F3929960.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/5ec6a268-1957-cfa2-82e1-902ebea60037.png)


- 同意画面が表示される
    - ちなみに同意したら履歴が残って次回から同意画面が表示されくなる
    - Users > Consents から履歴を削除できる
        
![9BBCCFDD-B332-4AF2-AE2D-0E2C73034215.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/e1bb3e87-621f-1e44-f751-d6fb8b616a43.png)

        
- ログインすると ID Token が取得できる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/f4477a2b-1e94-640a-aee0-1817b69c5b49.png)

- [JSON Web Tokens - jwt.io](https://jwt.io/) で ID Token をデコードすると User の情報が確認できる

![B1915FCE-5ABA-4039-9986-FD9AC5BB11BC.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/5bab13a1-844b-6860-834e-197bf50110ed.png)

- 管理画面で user の email を追加すると ID Token にも Email の情報が反映される

![22CABFB5-5B72-42C3-92F6-E98D0D078E34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/f8b63caf-a3ca-5a7b-d793-99ca657bbcb1.png)




## コード解説

### 認可コードを取得

`backend/app/Http/Controllers/GetAuthorizationCode.php`

```php
header(
    'Location: <http://localhost:8082/auth/realms/master/protocol/openid-connect/auth?'> .
    'response_type=code&' .
    'client_id=test-client&' .
    'redirect_uri=' . urlencode('<http://localhost/tokenRequest>') . '&' .
    'code_challenge=' . $codeChallenge . '&' .
    'code_challenge_method=S256&' .
    'scope=openid'
);

```

- Location
    - 認可エンドポイント
- response_type
    - 認可エンドポイントは必須のパラメーターとして `response_type` を要求
    - `code` `token` `id_token` `none`が指定可能
    - `scope` との組み合わせでフローが決まる
    - [OpenID Connect 全フロー解説 - Qiita](https://qiita.com/TakahikoKawasaki/items/4ee9b55db9f7ef352b47#1-response_typecode)
- client_id
    - 作成したクライアントの ID を指定
- redirect_uri
    - 認証完了後に認可コードをつけてリダイレクトされる uri
- scope
    - `openid` が指定されている場合に、 ID Token を取得し、 OIDC になる
    - `code` ならアクセストークンのみ取得
    - scope がとる値
        - [Final: OpenID Connect Core 1.0 incorporating errata set 1](http://openid-foundation-japan.github.io/openid-connect-core-1_0.ja.html#AuthRequest)

### PKCE について

[Auth0を利用してOAuth 2.0のPKCEを理解する | DevelopersIO](https://dev.classmethod.jp/articles/oauth-2-0-pkce-by-auth0/#toc-3)

> PKCEは認可コードの横取り攻撃の対策として定義されています。

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/81448fd4-5b76-57ed-4bd3-2b2da8640f45.png)


関連するのは
`code_challenge_method`  `code_challenge` `code_verifier`

`code_verifier` はトークンリクエストの時出てきます。

- `code_challenge_method` に記載されている方法で`code_verifier` を変換することで、`code_challenge` を作成します
- `code_challenge_method` `code_challenge` を認可リクエストの際に認可サーバーへ送信し、認可サーバー側で保存
- アクセストークンリクエストの時に`code_verifier` を送信し、認可サーバー側で`code_verifier` から`code_challenge`生成し、保存した`code_challenge`と比較することで、第３者から認可コードが送信されていないことを確認します。
    - `code_challenge_method` がとりえる値は `plain` or `S256`
        - Plain は変換しないので、`code_challenge` = `code_verifier` になり、セキュリティ的に脆弱
        - `S256`はSHA256ハッシュ化したものをbase64URLエンコード化する手法
            - `S256` にを選択するべき


### アクセストークンリクエスト

`backend/app/Http/Controllers/TokenRequest.php`

```php
$client = new Client();
$method = 'POST';
$uri = '<http://keycloak:8080/auth/realms/master/protocol/openid-connect/token>';
$options = [
    'client_id' => 'test-client',
    'client_secret' => '88a69be6-eac6-4d0e-bbd5-dfd9023a8acd',
    'grant_type' => 'authorization_code',
    'code' => $_GET['code'],
    'scope' => 'openid',
    'redirect_uri' => '<http://localhost/tokenRequest>',
    'code_verifier' => $codeVerifier,
];

$response = $client->request($method, $uri, ['form_params' => $options]);

```

リダイレクト URL に `http://localhost/tokenRequest` を指定しているので、このファイルに認可コードをもった状態でリダイレクトされてきます。

認可コードをトークンエンドポイントへ送信することで、ID Token を取得しています。

- `grant_type` 使用しているフローを指定します。今回は認可コードフローなので `authorization_code`を指定
- `code_verifier`
    - `code_challenge` の生成に使用した `code_verifier`
    - [OAuth2.0拡張仕様のPKCE実装紹介 〜 Yahoo! ID連携に導入しました - Yahoo! JAPAN Tech Blog](https://techblog.yahoo.co.jp/entry/20191219790463/#%60code_verifier%60%E3%81%A8%E3%81%AF)
   
    > code_verifierは43文字以上、128文字以下の文字列で、構成文字は[A-Z]/[a-z]/[0-9]/“-“/“.”/“_”/“~”となっています。

### 今回は出てこなかった Resource Server について

- [【第二弾】OAuth 2.0 + OpenID Connect のフルスクラッチ実装者が知見を語る - Qiita](https://qiita.com/TakahikoKawasaki/items/30fbd546935cea914e4f#22-%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%AE%E5%8F%97%E3%81%91%E5%8F%96%E3%82%8A%E6%96%B9)
    - クライアントアプリケーションから ID Token を受け取って ID Token の検証を行う
    - 検証方法はいくつかある
        - [Securing Applications and Services Guide](https://keycloak-documentation.openstandia.jp/master/ja_JP/securing_apps/index.html#_token_introspection_endpoint)

## 終わり

次回の記事は @Nyokki さんが MySQL 関連の記事を上げてくれるそうです。
@Nyokki さんがミライト社内で MySQL の勉強会をしてくれた時はわかりやすくてかなり勉強になりました。その節はありがとうございました。🙏