React & Keycloakでユーザー認証する

# React & Keycloakでユーザー認証する

## 目標

- Reactでユーザー認証
- 認証にはKeycloakを使う
- docker-composeで一発立ち上げ

## リポジトリ

https://github.com/iwanagat85/react-spa-keycloak

## Keycloak

今回はDBにMySQLを使う。

### Keycloakの設定

以下のdocker-composeを使って一度Keycloakを起動する。
`http://localhost:8080`を開いて管理画面からRealmとClient、Userを追加する。

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

services:

  keycloak:
    image: jboss/keycloak:6.0.1
    environment:
      KEYCLOAK_USER: admin
      KEYCLOAK_PASSWORD: password
      DB_VENDOR: MYSQL
      DB_ADDR: keycloak-db
      DB_DATABASE: keycloak
      DB_USER: keycloak
      DB_PASSWORD: password
    ports:
      - 8080:8080
    depends_on:
      - keycloak-db

  keycloak-db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: keycloak
      MYSQL_USER: keycloak
      MYSQL_PASSWORD: password
```

今回は以下のように登録した。

- Realm: `demo`
- Client:
    - Client ID: `demo-client`
    - RootURL: `http://localhost:3000/`
- User:
    - FirstName: `John`
    - LastName: `Smith`
    - EMail: `john.smith@mail.com`
    - Password: `passward`

### Installationファイルの取得

`Clients>demo-client>Installation`から`Keycloak OIDC JSON`をダウンロードする。

```json:keycloak.json
{
  "realm": "demo",
  "auth-server-url": "http://localhost:8080/auth",
  "ssl-required": "external",
  "resource": "demo-client",
  "public-client": true,
  "confidential-port": 0
}
```

### データのExport

Keycloakのプロセスに入って以下のコマンドを打ち込む。
投入したデータが指定したファイルにExportされたあとKeycloakが再起動する。

```terminal
# keycloak/bin/standalone.sh \
  -Djboss.socket.binding.port-offset=100 \
  -Dkeycloak.migration.action=export \
  -Dkeycloak.migration.provider=singleFile \
  -Dkeycloak.migration.realmName=demo \
  -Dkeycloak.migration.usersExportStrategy=REALM_FILE \
  -Dkeycloak.migration.file=/tmp/import-demo.json
```

Exportされたjsonを回収する。

```terminal
$ docker cp <CONTAINER ID>:/tmp/import-demo.json import-demo.json
```

### Dockerfileの作成

初回起動時にExportしたjsonをImport出来るように`Dockerfile`を作成する。

```dockerfile:Dockerfile
FROM jboss/keycloak:6.0.1

ADD import-demo.json /opt/jboss/keycloak/

CMD ["-b", "0.0.0.0", "-Dkeycloak.import=/opt/jboss/keycloak/import-demo.json"]
```

これでKeycloakの初回起動時にRealmとClient、UserがImportされるようになった。

## React

今回はTypeScriptで実装

### プロジェクトの作成

`create-react-app`等でざっくりプロジェクトを作る。

```terminal
$ npx create-react-app react-spa-keycloak --scripts-version=react-scripts-ts
```

`keycloak-js`をインストール。
見た目も多少魅せたいからBootstrap追加。

```terminal
$ npm install keycloak-js
$ npm install bootstrap
$ npm install react-bootstrap
```

`public`ディレクトリに前項で取得した`keycloak.json`を配置する。

### 

キモになる部分はこの部分。
keycloakによるログインが必須であること、認証が成功したときのみ`UserInfo.tsx`を
表示することを書くだけ。
煩わしい認証処理は全て`keycloak-js`がやってくれる！

```tsx:Secured.tsx
const Secured: React.FC = () => {

  const keycloak = Keycloak('/keycloak.json');
  const [keycloakState, setKeycloakState] = useState<KeycloakState>({keycloak: null, authenticated: false});

  useEffect(() => {
    keycloak.init({onLoad: 'login-required'})
      .success((authenticated) => {
        setKeycloakState({keycloak: keycloak, authenticated: authenticated});
      });
  }, []);

  if (keycloakState.keycloak !== null) {
    if (keycloakState.authenticated) {
      return (
        <div>
          <Alert variant="success">
            <Alert.Heading>Authenticated</Alert.Heading>
          </Alert>
          <UserInfo keycloakState={keycloakState}/>
          <Logout keycloakState={keycloakState}/>
        </div>
      );
    } else {
      return (
        <div>
          <Alert variant="danger">
            <Alert.Heading>Unable to authenticate</Alert.Heading>
          </Alert>
        </div>
      )
    }
  }

  return (
    <div>
      <p>Initializing Keycloak...</p>
    </div>
  );
};

export default Secured;
```

### Dockerfileの作成

ReactアプリケーションもDocker化する。

```dockerfile:Dockerfile
FROM node:latest

WORKDIR /app

COPY package.json /app
RUN npm install

COPY . /app
RUN npm run build

CMD ["npm", "start"]
```

## DockerCompose化

最後の仕上げ。

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

services:

  keycloak:
    build: ./keycloak
    environment:
      KEYCLOAK_USER: admin
      KEYCLOAK_PASSWORD: password
      DB_VENDOR: MYSQL
      DB_ADDR: keycloak-db
      DB_DATABASE: keycloak
      DB_USER: keycloak
      DB_PASSWORD: password
    ports:
      - 8080:8080
    depends_on:
      - keycloak-db

  keycloak-db:
    image: mysql:5.7
    volumes:
      - ./keycloak/db:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: keycloak
      MYSQL_USER: keycloak
      MYSQL_PASSWORD: password

  react-spa-keycloak:
    build: .
    ports:
      - 3000:3000
```

## 実行

```terminal
docker-compose up
```

`http://localhost:3000/`をブラウザで開く。

![localhost_3000_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233482/e72f17e1-e309-ed4a-f2dc-4058c2d122c3.png)

`john.smith@email.com/password`

![localhost_8080_auth.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233482/2eecc928-197a-a592-bd09-0087c0d7aa8a.png)

おおー！

![localhost_3000_secured.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233482/0d2f0066-b468-e26c-214d-eefafb0ca164.png)


# 感想

こんなにも簡単にフロントエンドのSSO認証を実装できるなんて！

# 参考文献

USER AUTHENTICATION WITH KEYCLOAK – PART 1: REACT FRONT-END
https://scalac.io/user-authentication-keycloak-1/