【Keycloak + SvelteKit】Keycloakをdockerで構築し、keycloak-jsを使ってアプリケーションからログインできるまで悩んだ記録

# はじめに

* 認証サーバーとして`Keycloak`、フロントエンドビルドツールとして`SvelteKit`を採用し、
構築するまでの手順を残していこうと思います。
* ちょっと長いですがお付き合いください。

# 環境

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/2996a114-a73e-51f3-cefe-fb43a3ba44fc.png" />

* MacBook Air
    * M2チップ 2022
* Docker
    * version 20.10.22
* Docker Compose
    * version v2.15.1
* keycloak-js
    * 20.0.5

# 1. ネットの記事にあったdocker-composeファイルで構築してみる

1. docker-compose.ymlの作成

    ```bash
    touch docker-compose.yml
    ```
    <details><summary>開く: docker-compose.yaml</summary>

    ```yaml
    version: '3'

    volumes:
      mysql_data:
          driver: local

    services:
      mysql:
          image: mysql:5.7
          volumes:
            - mysql_data:/var/lib/mysql
          environment:
            MYSQL_ROOT_PASSWORD: root
            MYSQL_DATABASE: keycloak
            MYSQL_USER: keycloak
            MYSQL_PASSWORD: password
      keycloak:
          image: quay.io/keycloak/keycloak:legacy
          environment:
            DB_VENDOR: MYSQL
            DB_ADDR: mysql
            DB_DATABASE: keycloak
            DB_USER: keycloak
            DB_PASSWORD: password
            KEYCLOAK_USER: admin
            KEYCLOAK_PASSWORD: Pa55w0rd
          ports:
            - 8080:8080
          depends_on:
            - mysql
    ```

    </details>

1. サーバー起動

    ```bash
    docker-compose up -d
    ```

    [http://localhost:8080](http://localhost:8080)でサーバーが立ち上がる

    立ち上がるまでまあまあ時間はかかります

# 2. レルム、クライアントの設定

1. `realm`の作成
1. `client`の作成
1. 作成できたら、作成したclientの設定画面から、`Valid redirect URIs`と`Web Origins`にフロントエンドサーバーを登録する。

    私はとりあえずローカルで試していたので`Valid redirect URIs`: `http://localhost:18080/*`、`Web Origins`: `http://localhost:18080`と設定しました。

1. ユーザーの作成

    1. `Users`から、`Add User`を選択し、ユーザー名を任意に設定し、`create`
    2. 作成したユーザーの設定画面が開くので、`Credentials`タブを開き、`Set Password`でパスワードを設定する。  
    ※ `Temporary`はOFFにしておく。

これで認証サーバー側の設定は完了。

# 3. フロント側の準備

1. Create Project

    ```bash
    npm create svelte@latest my-app
    cd my-app
    npm i
    ```

1. vite.config.tsの修正

    ```typescript:vite.config.ts
    import { sveltekit } from '@sveltejs/kit/vite';
    import { defineConfig } from 'vite';

    export default defineConfig({
      plugins: [sveltekit()],
      server: {          // 追加
        host: true,      // 追加
        port: 18080      // 追加
      }                  // 追加
    });
    ```

    portは自由に設定してください。
    ただし、`2. レルム、クライアントの設定`で設定したフロントサーバーのportに合わせるように。

1. keycloak-jsのinstall

    ```bash
    npm i keycloak-js
    ```

1. サーバー起動

    ```bash
    npm run dev
    ```

これでSvelteKitのwelcomeページが開くはず
[http://localhost:18080](http://localhost:18080)

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/e1c09eee-ae04-4897-7763-4119aa920480.png" width="300" />

# 4. keyclaok-jsを使って認証してみる

1. インスタンス生成
    
    ```vue:+page.svelte(抜粋)
    <script lang="ts">
      import Keycloak from 'keycloak-js'
    
      const keycloak = new Keycloak({
        clientId: "クライアント名",
        url: "http://localhost:8080/auth/",
        realm: "レルム名",
      })
    </script>
    ```
    
    `new Keycloak(*)`の部分にkeycloakの管理コンソールからconfigファイルをダウンロードしてきてそれを読ませる方法がネットにあがっていましたが、私のバージョンでは動きませんでした。
    
1. 認証処理を作成
    
    ```typescript:+page.svelte()
      // 認証状態
      let keycloakState = {
        keycloak: null,
        authenticated: false,
      }
    
      const keycloak = new Keycloak({
        clientId: "クライアント名",
        url: "http://localhost:8080/auth/",
        realm: "レルム名",
      })
    
      // 認証処理
      keycloak.init({
        onLoad: 'login-required',
        redirectUri: 'http://localhost:18080/',
        scope: 'openid'
      })
      .then((authenticated) => {
        // 認証成功時には認証状態を更新する
        keycloakState = {
          keycloak: keycloak, authenticated: authenticated
        };
      })
      .catch((e) => {
        console.error(e)
      });
    ```

1. 認証後にページ内容が表示されるようにawait blockを使う

    ```vue:+page.svelte
    <script lang="ts">
      let keycloakState = {
        keycloak: null,
        authenticated: false,
      }
    
      const signIn = async () => {
        const keycloak = new Keycloak({
          clientId: "クライアント名",
          url: "http://localhost:8080/auth/",
          realm: "レルム名",
        })    
        // 認証処理
        keycloak.init({
          onLoad: 'login-required',
          redirectUri: 'http://localhost:18080/',
          scope: 'openid'
        })
        .then((authenticated) => {
          // 認証成功時には認証状態を更新する
          keycloakState = {
            keycloak: keycloak, authenticated: authenticated
          };
        })
        .catch((e) => {
          console.error(e)
        });
      }
    </script>
    
    {#await signIn()}
      ...ログイン中
    {:then data}
      <section>
        省略  
      </section>
    {/await}
    ```

1. 画面を開いてみる
    
    ![スクリーンショット 2023-02-23 22.01.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/78e07c03-1763-55ba-f84d-893636aafe16.png)
    
    **KeyCloakログイン画面が表示された！**
    `2. レルム、クライアントの設定`で作成したユーザーでログインすると...


    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/e1c09eee-ae04-4897-7763-4119aa920480.png" width="300" />

    welcomeページにリダイレクトされた!
    成功！！かと思いきや、、、<font color="red">エラー発見</font>

    <font color="red">token requestで401エラー</font>

# 5. エラー調査

```json
  StatusCode: 401,
  Response Body: {
    "error":"unauthorized_client",
    "error_description":"Client secret not provided in request"
  } 
```

よくわからなかったのでAIに聞いてみた。

> **私**
> token endpointを叩いたときにステータスコード401になってしまいます。 エラー内容は以下の通りです。 
> {"error":"unauthorized_client","error_description":"Client secret not provided in request"} 
> 原因はなんだと思いますか？

>  <font color="red">**AI**</font>
> keycloakjsを使っているとのことですが、token endpointを叩くときにclient secretをリクエストに含めていますか？
> client secretはaccess typeがconfidentialの場合に必要なパラメーターです。
> client secretはKeycloakの管理コンソールで確認できます。

なるほどなるほど。
じゃあkeycloak-jsで**client secret**が設定できたらいいんだな！

> **私**
> keycloakjsを使っている時にclient secretを含める方法を教えてください

> <font color="red">**AI**</font>
> keycloakjsを使っている時にclient secretを含める方法は、keycloak-jsクライアントではサポートされていないようです。
> keycloak-jsクライアントはpublic keycloak clientとして動作し、client secretを必要としません

なるほどなるほど。
じゃあKeycloak管理コンソール上で今使っているclientを**credentialなclientからpublicなclientへ**変えればいいんだな！
と思ったが、**設定箇所が管理コンソール上に見当たらない...**

これもAIに聞いてみた

> **私**
> Dockerでquay.io/keycloak/keycloak:legacyのイメージを使用してkeycloakを起動していますが、
> Access Typeを変更できる箇所が見当たりません。どこにありますか？

> <font color="red">**AI**</font>
> quay.io/keycloak/keycloak:legacyのイメージを使用しているとのことですが、そのイメージはKeycloakの古いバージョン（3.4.3.Final）を含んでいます。 
> Access Typeの設定は、Keycloakの新しいバージョン（4.0以降）で導入されたものです。
> そのため、Access Typeが管理コンソールに見つからないのだと思われます。 最新版のKeycloakを使用することをお勧めします。

**優秀すぎる...**
すぐにdocker-compose.ymlを書き換えました

```yaml:docker-compose.yml
version: "3"
services:
  keycloak:
    image: jboss/keycloak:latest
    environment:
      KEYCLOAK_USER: keycloak
      KEYCLOAK_PASSWORD: password
    ports:
      - 8080:8080
```

次は、、<font color="red">コンテナが立ち上がらない...!!</font>
調べてみると**jboss/keycloak**は **Apple CPU搭載 Mac** 非対応のようです。。

# 6. jboss/keycloakをMacで起動する

https://qiita.com/tamaki_tech/items/3001577a5db1f0f43b56

こちらの記事を参考にしました

```bash
# 1. git clone
git clone https://github.com/keycloak/keycloak-containers.git

# 2.　ディレクトリ移動
$ cd keycloak-containers/server

# 3. ver19.0.3ブランチに移動する
$ git checkout 19.0.3

# 4. 任意のimage名・タグでdocker buildする
$ docker build ./ -t jboss/keycloak:19.0.3
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/12659da4-348d-eb78-e8f8-415909071c8f.png" width="400" />

作られていたらOK

このイメージを使うように`1. ネットの記事にあったdocker-composeファイルで構築してみる`で作った`docker-compose.yml`ファイルを修正する

```yaml:docker-compose.yml
version: '3'

services:
  keycloak:
    image: jboss/keycloak:19.0.3
    environment:
      KEYCLOAK_USER: admin
      KEYCLOAK_PASSWORD: password
    ports:
      - 8080:8080
```

起動OK。
再度`2. レルム、クライアントの設定`をし直す。
※ 👇の設定をOFFにした方が良さそう。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/dac6bad9-d45e-9dce-9d09-7a49910abd5a.png" width="300" />


#  7. 再挑戦

もう一度アプリ側からログインを試す。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/78e07c03-1763-55ba-f84d-893636aafe16.png" width="300"/>  

ドキドキ...

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/e1c09eee-ae04-4897-7763-4119aa920480.png" width="300" />

やったー！！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/eaf066fa-1f8d-9d73-d94b-f19e054d1830.png" width="400" />

ちゃんと状態を保存している`keycloakState`にも認証情報が入っていますね👍

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/40b0a2d8-b0b7-c334-618a-db6fcdb364e8.png" width="400" />

# 8. まとめ

#### **AI優秀**