msal-reactでAzure AD認証を実現する

## はじめに

ReactでAzure ADを使用したログイン認証や、Graph APIとの連携を実現したいことがあると思います。
自前で実装するのか・・・と落胆する必要はなく、Microsoftが[msal-react](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-react)といったライブラリを提供しています！msal-reactを使用することでAzure AD認証を簡単に自前のアプリに組み込むことができ、Graph APIとの連携も容易となるので紹介させてください！

## 事前準備

### Reactアプリ作成

今回も[vite](https://ja.vitejs.dev/)を使ってReactアプリを作成します。

```shell:実行コマンド
npm create vite@latest
```

作成後はAzure AD認証を実現する`@azure/msal-browser,@azure/msal-react`とHTTPライブラリ`axios`を入れておきます。

```shell:実行コマンド
npm install @azure/msal-browser @azure/msal-react axios
```

インストールしたら動作確認しておきましょう。

```shell:実行コマンド
npm run dev
```

#### 動作確認
![スクリーンショット 2023-06-17 11.06.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/7279b145-aa2c-aecb-5a71-cc3488918dac.png)
この画面が表示されていればOKです！
###  Azure AD ギャラリーに独自のアプリケーションを追加

Azure AD ギャラリーに独自のアプリケーションを追加します。
独自アプリケーションを事前に登録することで、Azure AD認証を組み込むことを可能とします。

下記画面から`独自のアプリケーションの作成`を選択します。![04f598e0-861e-6afc-be4f-a9b50b49054a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/d7cf02cb-6a0e-d66c-bcac-58d4faa1ee85.png)
名前は任意のものを入れていただいて構いません。今回は`ReactAuthTest`としています。
開発中アプリのため、`アプリケーションを登録してAzure ADと統合します`を選択します。

![6fb44b14-14ac-a56e-7776-0be103a4bad9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/142ba350-b3d7-34fc-bf64-7b4198f952ff.png)


Azure ADに登録されているユーザーのみ認証したいため`この組織ディレクトリのみに含まれるアカウント`を選択します。
リダイレクトURIはviteでデバッグするときのURI:`http://localhost:5173/`を指定し、アプリケーションの種別は`シングルページアプリケーション`を指定します。

![02063cc7-5772-00f9-1538-e4a25dcc77cb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/cc797545-50bb-e518-212b-886f41feb354.png)



赤枠部分の説明にも記載があるようにアプリケーションはシングルページアーキテクチャであり、承認コードフローを使用しないので、`アクセストークン`、`IDトークン`のチェックボックスを有効にします。

![8c4e595b-236a-d8ab-97a5-1c22ea2b2c37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/6ec4abce-3417-a6cc-1f27-fe84dfa4ec6a.png)

これで準備は完了です！

:::note info
下記2点はReactアプリケーションで使用する情報なのでメモしておきましょう。
:::

- アプリケーション（クライアント）ID
Azure AD ギャラリーに追加したアプリの概要ブレードを開き、赤枠部分のアプリケーション（クライアント）IDの値
![7bf3e66d-a97a-6edf-e2c0-8f3260851de0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/7609067a-c20a-8bd3-a130-b945cac0774f.png)
- プライマリ ドメイン
Azure Active Directoryの概要ブレードを開き、赤枠部分のプライマリ ドメインの値
![bf42c094-fded-3234-16e6-d8947a1cc166.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/03b671b6-c883-40e3-bde8-e40b5457ace2.png)


## 実装（Azure AD認証）

先ほどメモした情報を`.env`に環境変数として記載します。
viteでReactプロジェクトを作成した場合は、キー名のプレフィックスに`VITE`とつけることで環境変数として使用可能です。
- `VITE_CLIENT_ID`はメモしたアプリケーション（クライアント）IDの値
- `VITE_AUTHORITY`はメモしたプライマリ ドメインの値
- `VITE_REDIRECT_URI`はリダイレクトURIで指定したURI

```javascript:.env
VITE_CLIENT_ID = "メモしたアプリケーション（クライアント）IDの値"
VITE_AUTHORITY = "メモしたプライマリ ドメインの値"
VITE_REDIRECT_URI = "http://localhost:5173/"
```

まずは`main.jsx`に処理を追記します。
認証を行うインスタンスを作成し、`MsalProvider`を使用してそれぞれのコンポーネントが認証を実行できるようにします。
接続情報は`.env`から取得し、認証に成功した場合のアクセストークン保存先は`sessionStorage`を指定します。

```diff_jsx:main.jsx
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";

+ //インポートを追加
+ import { PublicClientApplication } from "@azure/msal-browser";
+ import { MsalProvider } from "@azure/msal-react";

+ //msalInstanceを作成
+ const msalInstance = new PublicClientApplication({
+   auth: {
+     //環境変数から認証情報を取得
+     //アプリケーション（クライアント）ID
+     clientId: import.meta.env.VITE_CLIENT_ID,
+     //プライマリ ドメイン
+     authority: `https://login.microsoftonline.com/${
+       import.meta.env.VITE_AUTHORITY
+     }`,
+     //リダイレクトURI
+     redirectUri: import.meta.env.VITE_REDIRECT_URI,
+   },
+   //キャッシュ先
+   cache: {
+     //セッションストレージ
+     cacheLocation: "sessionStorage",
+   },
+ });

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(
  <React.StrictMode>
+     <MsalProvider instance={msalInstance}>
      <App />
+     </MsalProvider>
  </React.StrictMode>
);
```

次に`App.jsx`に機能を追加します。先にライブラリから必要なコンポーネントやHooksをインポートしておきます。

```diff_jsx:App.jsx
+ import React, { useState } from "react";
+ import {
+   AuthenticatedTemplate,
+   UnauthenticatedTemplate,
+   useMsal,
+ } from "@azure/msal-react";
```

Azure AD認証を実行するログインボタンを作成していきます。
引数でGraph APIで要求する権限を指定する必要があるため、今回はユーザー情報を取得する権限`User.Read`を要求します。

:::note warn
要件に応じて使用したい権限は変わってくると思うので、詳しくは[公式ドキュメント](https://learn.microsoft.com/ja-jp/graph/permissions-reference)を参考にしていただければと思います。
:::


ログインボタンを押下したらProviderから受け取ったインスタンスに提供されているメソッド`loginRedirect`を実行して、Azure AD認証の画面へリダイレクトさせます。

```diff_jsx:App.jsx
+ //Azure AD認証でGraph APIに要求する権限
+ const loginRequest = {
+   //ユーザーを取得する権限
+   scopes: ["User.Read"]
+ };

+ /**
+  * ログインボタンを押下したらAzure ADの認証画面へリダイレクトさせる
+  * @returns ログインボタンコンポーネント
+  */
+ const LoginButton = () => {
+   //ProviderからAD認証用のインスタンスを取得
+   const { instance } = useMsal();
+   //ログインボタン実行時の関数
+   const handleLogin = () => {
+     //AD認証実現画面へリダイレクト。引数にGraph APIに要求する権限を設定
+     instance.loginRedirect(loginRequest);
+   };

+   return (
+     <>
+       <button onClick={() => handleLogin()}>ログイン</button>
+     </>
+   );
+ };

export default function App() {
  return (
    <div className="App">
+       {/*ログイン成功時に表示*/}
+       <AuthenticatedTemplate>
+         <div>ログインに成功しました！</div>
+       </AuthenticatedTemplate>
			
+       {/*未ログイン時に表示*/}
+       <UnauthenticatedTemplate>
+         <div>ログインしてください</div>
+         <LoginButton />
+       </UnauthenticatedTemplate>
    </div>
  );
}
```

`AuthenticatedTemplate`タグ内のコンポーネントはログイン後に表示されて、`UnauthenticatedTemplate`タグ内のコンポーネントは未ログイン状態の時に表示されます。
未ログイン状態ではログインボタンを表示したいので`UnauthenticatedTemplate`タグ内に配置します。

Azure AD認証の実装は完了したので動作確認します！

#### 動作確認

##### 初期表示

![スクリーンショット 2023-06-17 22.57.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/ed527146-c643-0e14-b826-0937005cfbef.png)

#####  ログインボタン押下後

Microsoftが提供するAzure AD認証のログイン画面が表示されました！

![スクリーンショット 2023-06-17 22.59.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/d019f596-85ef-83a4-a298-21274029666f.png)

##### ログイン完了後
ログイン後はリダイレクトURIに指定した`http://localhost:5173/`にリダイレクトされていますね。またログインに成功したため、`AuthenticatedTemplate`内の文言が表示されています！

![スクリーンショット 2023-06-17 23.00.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/cece9413-2522-332e-0553-56105233adeb.png)

##### Session Storage

`Session Storage`を確認すると、取得した`IdToken`など保存されていますね！

![b1971585-29d1-f585-fae4-a3a3e39073be.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/ebe48455-5a61-640f-aedc-06c16d645f14.png)

次はログインした状態でGraph APIと連携できるよう実装を進めていきます。

## 実装（Graph APIと連携）

アクセストークンを引数で受け取り、Graph APIを使用してユーザー情報を取得する関数を作成します。

```diff_javascript:App.jsx
+ /**
+  * Graph APIをコールしユーザー情報を取得する
+  * @param {Object} accessToken 
+  * @returns Graph APIで取得したユーザー情報
+  */
+ const callGraphApi = async (accessToken) => {
+   //受け取ったアクセストークンをヘッダーに設定してGraph APIをコール
+   const response = await axios.get("https://graph.microsoft.com/v1.0/me", {
+     headers: {
+       Authorization: `Bearer ${accessToken}`,
+     },
+   });
+   return response.data;
+ };
```
:::note warn
今回はユーザー情報を取得するエンドポイント`GET /me`を使用していますが、メールを作成したいなど要件に応じて使用するエンドポイントは変わってきますので、こちらも[公式ドキュメント](https://learn.microsoft.com/ja-jp/graph/api/overview?view=graph-rest-1.0)を参照していただければと思います。
:::

アクセストークンを取得してGraph APIを実行し、取得したユーザー情報を表示するコンポーネントを作成します。

```diff_jsx:App.jsx
//Azure AD認証でGraph APIに要求する権限
const loginRequest = {
  //ユーザーを取得する権限
  scopes: ["User.Read"]
};

+ /**
+  * Graph APIをコールし、取得したユーザー情報を表示するコンポーネント
+  * @returns ユーザー情報表示コンポーネント
+  */
+ const ProfileContent = () => {
+   //accountsはログイン時に取得したユーザー情報
+   const { instance, accounts } = useMsal();
+   //Graph APIで取得するデータのState
+   const [graphData, setGraphData] = useState(null);
+ 
+   function RequestProfileData() {
+     //★accuireTokenSilentを使用してアクセストークンを取得する。
+     instance
+       .acquireTokenSilent({
+         ...loginRequest, //Graph APIに要求する権限
+         account: accounts[0],//ログインユーザー情報
+       })
+       //取得に成功したらアクセストークンをGraph API実行関数に渡して実行
+       .then((response) => {
+         callGraphApi(response.accessToken).then((data) => {
+           //Graph APIのデータをStateに保存
+           setGraphData(data);
+           console.log(data);
+         });
+       });
+   }

+   return (
+     <>
+       <h5 className="">Welcome {accounts[0].name}</h5>
+       <br />
+     　{/*Graph APIのデータがStateに保存されている場合*/}
+       {graphData ? (
+         <div>
+           <div>id:{graphData.id}</div>
+           <div>名前:{graphData.displayName}</div>
+         </div>
+       ) : (
+         {/*Graph APIのデータがStateに保存されていない場合*/}
+         <button variant="secondary" onClick={RequestProfileData}>
+           Graph API実行
+         </button>
+       )}
+     </>
+   );
+ };
```
★マークの処理について説明すると下記流れになります。
1. `acquireTokenSilent`メソッドでAzure AD認証で取得したユーザー情報とGraph APIに要求する権限を引数に設定してアクセストークンを取得する。
2. アクセストークンを`callGraphApi`の引数に設定して、ユーザー情報を取得する。
3. ユーザー情報をStateに保持し、画面に表示する。


最後に作成したコンポーネントをログイン時に表示するようにします。

```diff_jsx:App.jsx
export default function App() {
  return (
    <div className="App">
      {/*ログイン成功時に表示*/}
      <AuthenticatedTemplate>
-        <div>ログインに成功しました！</div>
+        <ProfileContent />
      </AuthenticatedTemplate>

      {/*未ログイン時に表示*/}
      <UnauthenticatedTemplate>
        <div>ログインしてください</div>
        <LoginButton />
      </UnauthenticatedTemplate>
    </div>
  );
}
```

実装完了です！動作確認していきます。

#### 動作確認

##### ログイン後画面
ログイン後に表示される画面です。ここで`Graph API実行`ボタンを押下します。
![スクリーンショット 2023-06-24 10.04.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/e1eb354b-e975-93b4-4870-2eeedfad82dc.png)
##### Graph API実行ボタンを押下後
![0eb81154-0b44-3d16-ab80-aa6ab81144a7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/a3676080-bc08-39c0-ee18-9bea353b73d7.png)



Graph APIで取得した情報が無事表示されましたね！

##  おわりに

msal-reactはいかがでしたでしょうか？Azure AD認証が簡単に実現できたと思います！
また、ログイン後のアクセストークン取得やGraph APIとの連携も簡単です。
ReactでAzure AD認証を使用する場合はぜひ使ってみてください！