OAuth 2.0 承認と Azure Active Directory を使用して Azure API Management で API を保護してみよう

# はじめに
溜まっていた下書きの棚卸しです🤦‍♂️
Azure API Managementを触っていく中で、認証方法に関するチュートリアルを見つけ自己流にアレンジしてみました。
今回、React(ローカル)からAPI Managementを通してAzure FunctionsにAPIを投げたいと思います。
(Azure Functionsは事前にデプロイ済みのものを使います。)

※チュートリアル

https://learn.microsoft.com/ja-jp/azure/api-management/api-management-howto-protect-backend-with-aad

# 環境
* Windows11
* Visual Studio Code
* 事前にデプロイ済みのAzure Functions
* 事前にリソース作成済みのAPI Management

# ①API ManagementにAzure Functionsを追加する
1. API Managementのリソースに移動します
2. 左側の【API】タブをクリックします
1. 【+Add API】をクリックし、【Function App】を選択します
1. 【Browser】をクリックします
![スクリーンショット 2023-04-29 081712.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/895dff90-9143-170e-fdf6-5fdd9c75f6d1.png)
5. 事前に準備したFunctionをインポートします
5. 【Create】ボタンを押して完了します

# ②Azure Functionsで認証プロバイダーを追加する
1. Functionのリソースに移動します
1. 【認証】タブをクリックします
1. IDプロバイダーを追加します
![スクリーンショット 2023-04-30 170034.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/01182e7e-bec3-8329-3a1a-7f09cd7f748b.png)
3. IDプロバイダーにMicrosoftを選択します
3. 項目を埋め【追加】をクリックします
<details>

![スクリーンショット 2023-04-30 170200.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/d330eed3-2517-29c4-b425-2e59ebdbe426.png)

</details>
6.追加後に作成されたクライアントIDをメモリします
<details>

![スクリーンショット 2023-04-30 235323.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/ae2f7b7b-03de-cee4-95cb-96009de38320.png)

</details>
7. 認証を追加したことでAPI実行に認証情報が含まれていないとエラーが出ることを確認します
<details>

![スクリーンショット 2023-04-30 170340.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/63771131-e3d3-b7ac-c4ac-54fd17457e79.png)

</details>

# ③API Managementに要求を事前承認する検証ポリシーを構成する
APIManagementのJWT検証とReactアプリからAPIを投げるためCORSを構成します
APIデザインのコードにインバウンドタグを以下のように編集します
テナントIDとクライアントIDはご自身のものに書き換えてください
```xml
    <inbound>
        <base />
        <set-backend-service id="apim-generated-policy" backend-id="{FunctionID}" />
        <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
            <openid-config url="https://login.microsoftonline.com/{テナントID}/.well-known/openid-configuration" />
            <required-claims>
                <claim name="aud" match="all">
                    <value>api://{②で作成されたクライアントID}</value>
                </claim>
            </required-claims>
        </validate-jwt>
        <cors allow-credentials="false">
            <allowed-origins>
                <origin>http://localhost:3000</origin>
            </allowed-origins>
            <allowed-methods>
                <method>GET</method>
            </allowed-methods>
            <allowed-headers>
                <header>*</header>
            </allowed-headers>
        </cors>
    </inbound>
```

# ④ReactアプリをAzure ActiveDirectoryに登録します
1. Azure Active Directoryに移動し、【アプリケーションの登録】をクリックします
1. 項目を埋め【登録】をクリックします
![スクリーンショット 2023-04-30 074919.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/a9ffe4ec-1ebc-480e-fba7-cf3d51b2e407.png)
3. 【認証】タブに移動しプラットフォームの追加でSPAを選択し、リダイレクトURLに「http://<span>localhost:3000</span>」を入力します
3. APIのアクセス許可に②で追加したFunctionsの認証を許可するように設定します

# ⑤Reactアプリを作成します
1. 「npx create-react-app app --template typescript」でReactアプリを作成します
1. axiosと@azure/msal-browserをインストールします
1. App.tsxを編集します

```typescript
import React from 'react';
import './App.css';
import axios from 'axios';
import { PublicClientApplication } from "@azure/msal-browser";

const msalConfig = {
    auth: {
        clientId: 'クライアントID',
        redirectUri: 'http://localhost:3000',
        authority: 'https://login.microsoftonline.com/{テナントID}',
    }
};

const msalInstance = new PublicClientApplication(msalConfig);
function App() {
  const getAPI = async () => {
    try
    {
      const accounts = await msalInstance.getAllAccounts();

      const silentRequest = {
          scopes: ['api://{FunctionのクライアントID}/user_impersonation'],
          account: accounts[0]
      };
      const result = await msalInstance.acquireTokenPopup(silentRequest);

      await axios.get('https://{APIManangementのリソース名}.azure-api.net/{表示名}/{API名}', {
        headers: {
          'Authorization': `Bearer ${result.accessToken}`,
        }
      })
      .then(res => {
        console.log(`Response:\t${res.status}`);
        console.log(`Content:\t${res.data}`);
      })
    }
    catch (error)
    {
      console.log(error);
    }
  }

  return (
    <div className="App">
      <button className="btn btn-primary" onClick={() => getAPI()}>Click me</button>
    </div>
  );
}

export default App;

```
4. 「npm start」でアプリを実行し、画面上のボタンをクリックするとAPIが実行され実行結果がコンソールに表示されると思います


# 所感
MSのチュートリアルを自己流に変換してみました。
現状だとReactからFunctionにもリクエストを送れる状態になってしまっているため、機会があればAPI Management経由しか受け付けないように構成をしてみたいと思いました。
※IPのアクセス制御方法は以下

https://learn.microsoft.com/ja-jp/azure/azure-functions/ip-addresses?tabs=portal#ip-address-restrictions