ReactでMSAL試してみた

## はじめに

### 本記事の目的

- ReactアプリにおけるOIDC・OAuth準拠した認証・認可の実装例として、Microsoft社が提供するフレームワークであるMicrosoft Authentication Library(以降、MSAL)を試してみる。

### 対象読者

- OIDC及びOAuthに関する基礎知識を持つ、フロントエンドエンジニアを対象読者として想定する。

### 環境構成イメージ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3562653/715ebba7-9722-17f7-5776-21d7667e542a.png)

## OpenID Provider設定

### Dockerコンテナ設定・起動

- Keycloak及びPostgresをDocker基盤上で動かすために、Composeファイルを作成する。

```yaml
version: '3'
services:
  postgres:
    image: postgres:15
    restart: always
    environment:
      POSTGRES_USER: $POSTGRES_USER
      POSTGRES_PASSWORD: $POSTGRES_PW
      POSTGRES_DB: keycloak
    ports:
      - 5432:5432
    volumes:
      - ./postgres:/var/lib/postgresql/data
  keycloak:
    image: quay.io/keycloak/keycloak:23.0.0
    container_name: keycloak
    tty: true
    stdin_open: true
    ports:
      - 8080:8080
    environment:
      KEYCLOAK_ADMIN: $KEYCLOAK_ADMIN
      KEYCLOAK_ADMIN_PASSWORD: $KEYCLOAK_ADMIN_PASSWORD
      KC_DB: postgres
      KC_DB_USERNAME: $POSTGRES_USER
      KC_DB_PASSWORD: $POSTGRES_PW
      KC_DB_URL_PORT: 5432
      KC_DB_URL_HOST: postgres
      KC_DB_URL_DATABASE: keycloak
    command:
      - "start-dev"
    depends_on:
      - postgres
```
:::note warn
start-devオプションを用いて、Keycloakを開発モードで起動している。Production環境でKeycloakを利用する場合には、本記事の設定をそのまま流用しないように留意すること。
:::
- Docker Composeコマンドを実行し、コンテナ群を起動する。
```bash
docker-compose up
```


### Keycloak設定

- Keycloakの管理画面にログインする。
    - ブラウザでlocalhost:8080にアクセスする。
    - Docker Compose時に指定した`KEYCLOAK_ADMIN`及び`KEYCLOAK_PW`の値を用いて、ログインする。
- `Create realm`から、Realmを作成する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3562653/e0acbccc-c267-ec05-0621-645efdc8acd1.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3562653/29c78cd0-ef86-4c2e-2a60-c56df1ecba75.png)

- `Clients`>`Create client`から、OIDC Clientを作成する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3562653/803baec7-c50d-cfe0-a3d5-a01233378800.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3562653/fd778283-3393-be03-e829-232e0437bf67.png)

:::note info
OIDC Clientを作成する際、Redirect URI及びWeb originsにReactアプリケーションの情報を登録すること。
:::

- `Users`>`Add user`から、動作確認で利用するログインユーザを作成する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3562653/e51c1c26-09dc-9ad9-464d-f5c3f5d0d551.png)

- `Realm settings`>`OpenID Endpoint Configuration`から、OIDCエンドポイント情報を取得する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3562653/9f655c64-13d8-7c33-35bf-a32229d1b88c.png)

## Reactアプリ構築

- Reactプロジェクト作成した上で、MSAL系ライブラリをインストールする。

```bash
# Reactプロジェクトを作成
npx create-react-app myApp --template typescript

# msal系ライブラリをインストール
npm install @azure/msal-react @azure/msal-browser

# 開発サーバ起動
npm start
```

- KeycloakのOIDCエンドポイント情報を.envファイルに格納する。
- `index.tsx`にて、認証インスタンスを実装する。

```typescript
export const MsalInstance = new msal.PublicClientApplication({
  auth: {
    // protocolModeにOIDCを指定することで、Azure以外のOPにも対応可能
    protocolMode: msal.ProtocolMode.OIDC,
    authorityMetadata: JSON.stringify({
      authorization_endpoint: process.env.REACT_APP_AUTHORIZATION_ENDPOINT,
      token_endpoint: process.env.REACT_APP_TOKEN_ENDPOINT,
      issuer: process.env.REACT_APP_REALM_ENDPOINT,
      userinfo_endpoint: process.env.REACT_APP_USERINFO_ENDPOINT,
    }),
    clientId: process.env.REACT_APP_CLIENT_ID ?? "poc-client",
  },
});
```

- `App.tsx`にて、認証処理ロジックを実装する。

```react
import React from "react";
import "./App.css";
import {
  useMsal,
  AuthenticatedTemplate,
  UnauthenticatedTemplate,
} from "@azure/msal-react";

function App() {
  const { instance } = useMsal();
  return (
    <div className="App">
      <h1>MSAL App</h1>
      <AuthenticatedTemplate>
        <div>Login Succeeded</div>
      </AuthenticatedTemplate>
      <UnauthenticatedTemplate>
        <button onClick={()=>instance.loginPopup()}>Login via Keycloak</button>
      </UnauthenticatedTemplate>
    </div>
  );
}

export default App;
```

## 動作確認

### 機能観点での動作確認

- 未認証でフロントエンドアプリにアクセスした場合、ログインボタンが表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3562653/eb46aa75-0ac3-d665-6b9c-c73ea6123400.png)

- ログインボタンを押下すると、ポップアップでKeycloakのログイン画面に遷移する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3562653/959a4181-85a7-17fc-cdd3-189a111b32f4.png)

- Keycloakの認証情報を入力すると、ログイン成功画面にリダイレクトされる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3562653/a8cd9018-0cc8-02ec-c14d-532c692d0fc1.png)

### セキュリティ観点での動作確認

- 認証リクエストのクエリパラメータを確認したところ、
    - `response_type`に`code`が指定されており、認可コードフローが使用されている。
    - `code_challenge`及び`code_challenge_method`が指定されており、PKCEによる認可コード横取り対策が実装されている。
    - `nonce`が指定されており、リプレイアタック対策が実装されている。
    - `state`が指定されており、CSRF対策が実装されている。
- ブラウザのSession Storageに格納されたIDトークンを[jwt.io](https://jwt.io/)を用いてデコードしたところ、`at_hash`が含まれており、トークンインジェクション対策が実装されている。

## 参考
- [Azure Active Directory B2C を使用して独自の React アプリケーションで認証を有効にする](https://learn.microsoft.com/ja-jp/azure/active-directory-b2c/enable-authentication-react-spa-app#step-1-create-a-react-app-project)
- [Using MSAL in a React SPA with Keycloak](https://www.frodehus.dev/use-keycloak-and-msal-with-react-spa/)
- [OAuth 2.0 Security Best Current Practice](https://www.ietf.org/archive/id/draft-ietf-oauth-security-topics-24.html)
- [2. OAuth 2.0 Security Best Current Practice](https://www.authlete.com/ja/resources/videos/20200131/02/)

## 注意事項
- 本記事は万全を期して作成していますが、お気づきの点がありましたら、ご連絡よろしくお願いします。
- なお、本記事の内容を利用した結果及び影響について、筆者は一切の責任を負いませんので、予めご了承ください。