Microsoft Entra Verified ID を使ってみた（②発行編）

前回の記事でVerified IDの環境構築を行いましたが、今回はMicrodoft Entra Verified ID を使いデジタル証明書を発行する簡単なアプリケーションを作成します。

* [Microsoft Entra Verified ID を使ってみた（①環境構築編）](https://qiita.com/AsukaSeki/items/cccc944244cd9deaeab9)

コードは Microsoft 公式で公開されているこちらを参考にしております。

https://github.com/Azure-Samples/active-directory-verifiable-credentials-dotnet

## 前提条件
今回実装する環境は、下記の通りとします。
  - Backend
    - ライブラリ：ASP .NET
    - ランタイム：.NET 6
  - Frontend
    - フレームワーク：Angular v14
      - [Angular CLI v14](https://github.com/angular/angular-cli)は既にインストールされているものとします
    - Node バージョン： Node v16.18.0

また、お手持ちのモバイル端末に Microsoft Authenticator がない場合は下記の URL からインストールします。
  - https://www.microsoft.com/ja-jp/security/mobile-authenticator-app
  - Microsoft Authenticator を初回起動した際に Microsoft アカウントへのサインインが聞かれますが、Verified ID では不要ですのでスキップして構いません。

## ソースコード
全体のソースコードはこちらのGitHubに公開いたしました。

https://github.com/AsukaSeki/verified-id-sample

今回Frontend, Backendの2つの実装をご紹介しますが、下記のそれぞれ下記のディレクトリ直下をご参照ください。
* Backend：`backend`ディレクトリ
* Frontend：`frontend`ディレクトリ

## まずは動かしてみる
まずは、上記ソースコードを動かしてみましょう。

### 1. App Services の作成
Backendはインターネットゾーン経由でアクセスできる必要がありますので、App Servicesを作成しておきます。

- 名前：任意の名前
- ランタイムスタック：.NET6
- オペレーティング システム：Windows
- 地域：Japan East
- App Serviceプラン：無ければ新規作成

他はデフォルトで問題ありません。

![23.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962964/143d7614-35e1-4418-f84e-6b3d3dac4763.png)

### 2. Backendアプリケーション設定の編集
`backend/VerifiedIdApi/appsettings.json`を編集し、お使いのVerified IDの環境に書き換えます。

* Endpoint：`https://verifiedid.did.msidentity.com/v1.0/`
* VCServiceScope：`3db474b9-6a0c-4840-96ac-1fceb342124f/.default`
* Instance: `https://login.microsoftonline.com/{0}`
* TenantId: ADのテナントID
  * Microsoft Entra管理ページのAzure Active Directory > Overviewから取得できます
    
    ![2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962964/b7d0ece9-f31b-c2b1-ebd0-e63d8b95fd4f.png)

* ClientId: Verified ID用ADアプリケーションのID
  * Microsoft Entra管理ページのAzure Active Directory > Applications > アプリの登録から取得できます

    ![3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962964/f2d70463-2e24-5bbc-e385-57007415f8d6.png)

* ClientSecret: Verified ID用ADアプリケーションのシークレットの値
  * Microsoft Entra管理ページのAzure Active Directory > Applications > アプリの登録 > Verified ID用アプリ > 証明書とシークレットから取得できます
  * ただし、シークレットの値に関してはシークレット登録直後しか確認ができません。

    ![6.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962964/31d731be-d1d4-0ef2-4ab8-4366e3d55ca9.png)

* IssuerAuthority および VerifierAuthority: 分散化識別子 (DID)
  * Microsoft Entra管理ページの検証済みID > 組織の設定から取得できます
    
    ![4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962964/138b59b1-7711-c853-0bf2-2315dff378a3.png)

* CredentialManifest: マニフェスト URL
* Type: 資格情報の種類
  * Microsoft Entra管理ページの検証済みID > 資格情報 > 詳細から取得できます

    ![5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962964/a51d5a7b-01dc-9a0f-2a34-477a2431d7e7.png)


* APIKey: APIキー
  * Verified ID～Backend間の認証用APIキーです
  * こちらは任意の値を設定してください。

```json
{
  "VerifiedId": {
    "Endpoint": "https://verifiedid.did.msidentity.com/v1.0/",
    "VCServiceScope": "3db474b9-6a0c-4840-96ac-1fceb342124f/.default",
    "Instance": "https://login.microsoftonline.com/{0}",
    "TenantId": "<Tenant ID>",
    "ClientId": "<AD Application Client ID>",
    "ClientSecret": "<AD Application Client Secret>",
    "IssuerAuthority": "<DID>",
    "VerifierAuthority": "<DID>",
    "CredentialManifest": "<Manifest URL>",
    "Type": "<Type>",
    "ApiKey": "<API Key>"
  }
}
```

### 3. App Servicesへのデプロイ
アプリケーション設定が完了したら、作成したApp Servicesにデプロイします。

### 4. Frontend環境変数の編集
`frontend/src/environemnts/environment.ts`を編集し、`apiEndPoint`を先ほどデプロイしたApp Servicesのドメインに書き換えます。ドメインの最後に`/`を忘れずに追加します。

```ts
export const environment = {
  production: false,
  apiEndPoint: 'https://<API EndPoint>/'
};
```

### 5. Frontendの起動
下記コマンドを実行し、Frontendを起動します。
* ライブラリのインストール
  ```
  > npm ci
  ```

* 実行
  ```
  > ng serve --open
  ```

### 6. 動作確認
Frontendが起動したら、自動的に`http://localhost:4200/`にアクセスされます。

アプリケーション起動時、このようなメイン画面が表示されるため証明書の発行をしてみます。

![7.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962964/76d1d1a4-55dc-c6ea-8711-2a328af8d2b8.png)


### 証明書発行
まず、証明書の発行を動かしてみます。

1. こちらの画面で氏名を入力します。氏名を入力したら「証明書を発行する」ボタンを押します。

    ![8.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962964/0cf0bdc6-9a4a-09f7-d0b2-a5630df4e2f9.png)

1. すると、このようにQRコードが表示されます。こちらのQRコードをモバイル端末のカメラアプリやMicrosoft Authenticatorで読み取ります。

    ![9.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962964/7acdb56b-8e38-50da-b95c-3e19850ebf42.png)

1. QRコードを読み取ると、Authenticatorでは「確認コードを入力してください」というPIN入力画面に切り替わります。アプリケーション上に下記のように表示されているPINをAuthenticatorで入力します。

    ![10.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962964/4adb0d7c-c455-b471-5182-9d9a728304b6.png)

1. PINの入力が完了し「検証済みIDの追加」画面で追加すると、証明書発行が完了します。

    ![11.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962964/262a1a9b-ff8f-d959-fdb7-0188fa0bf6fd.png)

1. Authenticatorでは、このような証明書が登録されます。
    - 発行した証明書の「Last Name」「First Name」には、入力した氏名が表示されます。
    - この証明書はAuthenticatorのトップ画面の「検証済みID」からいつでも確認ができます。

    ![13.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962964/c42ee8e4-375e-d4ff-d871-73a89c21440b.png)


## 証明書発行の概要

デジタル証明書の発行は下記の流れで行われます。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962964/76a77389-d449-f5eb-cb31-c4c06663c67f.png)


1. ユーザーはアプリケーションで氏名などの属性を入力し、デジタル証明書発行のリクエストを依頼します。
1. Verified ID はアプリケーションへ URL や PIN を返却し、アプリケーションはそれを元に QR コードを表示します。
1. ユーザーは QR コードを読み取り、デジタル証明書の発行を許可します。
1. Verified ID は QR コードが読み取り、デジタル証明書の発行が許可されたことをアプリケーションへ通知し、アプリケーションはデジタル証明書の発行が完了したことを画面表示します。
    * このVerified IDからの通知のために、Backend側はApp Servicesに公開する必要がありました。

## 実装のポイント（Frontend）

#### QRコードの生成ライブラリ
AngularでQRコードを生成するには、[angularx-qrcode](https://www.npmjs.com/package/angularx-qrcode)のライブラリが便利です。

[デモサイト](https://cordobo.github.io/angularx-qrcode/)

[Verified APIのリクエスト結果](https://learn.microsoft.com/en-us/azure/active-directory/verifiable-credentials/issuance-request-api#successful-response)でもQRコードのBase64が取得できるのですが、このライブラリを利用することでQRコードのサイズや色なども柔軟に変更できます。

#### デジタル証明書発行ページ
この画面では氏名を入力させ、証明書を発行します。

ページにステータスを持たせ、Backendから返却されたステータスによってQRコードや証明書発行完了などの表示切り替えを行います。

1. ユーザーは氏名を入力します。
1. 「証明書を発行する」ボタンを押すと、Backendの証明書発行のリクエストAPIを呼び出します。
1. BackendからURLやPIN等が返却されたら、URLをもとにQRコードを生成し表示します。
1. 証明書発行のステータス取得API を定期的に呼び出し、「QR 読み取り中」や「発行完了」などのステータスに応じて QR コード表完了表示などに切り替えます。

## 実装のポイント（Backend）
Backendでは、下記のAPIを作成しています。

* 証明書発行コントローラー
  * 証明書発行のリクエストAPI
  * Verified IDからコールバックされた時のAPI
  * 証明書発行のステータス取得API

ポイントとしては証明書の発行がまだ途中なのか、完了したのかといった状態をメモリキャッシュで保管しておき、都度この値を更新しています。

### 証明書発行コントローラー

#### 証明書発行のリクエストAPI
* Frontendで入力された氏名を受け取り、Verified IDの証明書発行APIを呼び出します。
  * 詳細：[Issuance request payload](https://learn.microsoft.com/en-us/azure/active-directory/verifiable-credentials/issuance-request-api#issuance-request-payload)
  * APIのURLは下記の通りです。
    * POST `https://verifiedid.did.msidentity.com/v1.0/verifiableCredentials/createIssuanceRequest`
  * Request Bodyで色々と値を設定する必要がありますが、ポイントは下記の3点です。
    * `callback.url`：Verified IDからコールバックされる先のURLを指定します。具体的なコールバックの内容や、コールバック時の動きついては後述の「Verified IDからコールバックされた時のAPI」をご参照ください。
    * `callback.state`：コールバックされた際にこちらのリクエストと紐づけるためのIDです。
    * `claims`：ここには証明書に登録するユーザー属性等を設定します。今回はユーザーが入力した氏名とIDを設定しています。
    * `pin`：QRコードが読み取られた後に、証明書発行前にさらにAuthenticator内で認証させたい場合に設定します。
  * APIの呼び出し成功すると、URLや有効期限が返却されます。
    * 詳細：[Successful response](https://learn.microsoft.com/en-us/azure/active-directory/verifiable-credentials/issuance-request-api#successful-response)
* 状態を管理し、コールバック時にどのリクエストかを特定するため、`state`の値はメモリキャッシュのキーに登録します。
  ```csharp
  string state = Guid.NewGuid().ToString();

  ...

  issuanceRequest.Callback.State = state;

  ...

  // 証明書発行APIを呼び出す
  HttpResponseMessage res = await HttpClient.PostAsync(Option.ApiEndpoint + "verifiableCredentials/createIssuanceRequest", new StringContent(jsonString, Encoding.UTF8, "application/json"));

  ...

  var cacheData = new CacheData
  {
      Status = "notscanned",
      Message = "Request ready, please scan with Authenticator",
      Expiry = result.Expiry.ToString()
  };

  Cache.Set(state, JsonConvert.SerializeObject(cacheData));
  ```

#### Verified IDからコールバックされた時のAPI
* ユーザーがAuthenticatorでの操作を行ったタイミングで、Verified APIからBackendへコールバックがされます。
  * 詳細：[Callback events](https://learn.microsoft.com/en-us/azure/active-directory/verifiable-credentials/issuance-request-api#callback-events)
  * コールバックされたときに取得できる情報には`requestStatus`があり、そちらでどういった操作がされたかを判断できます。この`requestStatus`には大きく分けて2種類があります。
    * QRコードが読み取られたとき：`request_retrieved`
    * 証明書が承認されたとき：`issuance_successful`
* このAPIでは、下記の通りコールバックされたときに`requestStatus`の値によってメモリ内の状態を更新します。
  ```csharp
  // ①QRコードが読み取られた時は「request_retrieved」が返される
  if (issuanceCallback.RequestStatus == "request_retrieved")
  {
      var cacheData = new CacheData
      {
          Status = "request_retrieved",
          Message = "QR Code is scanned. Waiting for issuance...",
      };
      Cache.Set(state, JsonConvert.SerializeObject(cacheData));
  }

  // ②証明書が承認された時は「issuance_successful」が返される
  if (issuanceCallback.RequestStatus == "issuance_successful")
  {
      var cacheData = new CacheData
      {
          Status = "issuance_successful",
          Message = "Credential successfully issued",
      };
      Cache.Set(state, JsonConvert.SerializeObject(cacheData));
  }
  ```

#### 証明書発行のステータス取得API
* このAPIではメモリ内の状態を参照し、ステータスを返します。
  ```csharp
  if (Cache.TryGetValue(state, out string buf))
  {
      var cacheData = JsonConvert.DeserializeObject<CacheData>(buf);

      if (cacheData != null)
      {
          var result = new IssuanceResponseViewModel
          {
              Status = cacheData.Status,
              Message = cacheData.Message
          };

          if (!string.IsNullOrEmpty(cacheData.Expiry))
          {
              result.Expiry = cacheData.Expiry;
          }

          return result;
      }
  }
  ```

## まとめ
いくつかのAPIを呼び出すだけで、簡単にデジタル証明書の発行ができます。

次回は、発行されたデジタル証明書の検証についてをご紹介します。

他にも、Verified IDでは証明書のカスタマイズなど様々な機能が提供されているので試してみたいと思います。

## 関連記事およびサンプル
* 前回の記事
  * [Microsoft Entra Verified ID を使ってみた（①環境構築編）](https://qiita.com/AsukaSeki/items/cccc944244cd9deaeab9)
* [本記事のコードGitHub](https://github.com/AsukaSeki/verified-id-sample)

## 参考資料
* サンプルコード
  * [Microsoft公式GitHub](https://github.com/Azure-Samples/active-directory-verifiable-credentials-dotnet)
* 公式リファレンス
  * [Issuance request payload](https://learn.microsoft.com/en-us/azure/active-directory/verifiable-credentials/issuance-request-api#issuance-request-payload)
    * 証明書発行のAPIリクエスト