クイック スタート: msal.js を使用して Dataverse の SPA アプリケーションを登録して構成する

このトピックでは、msal.js および Cross-origin Resource Sharing (CORS) を使用して Microsoft Dataverse のデータにアクセスするために最も単純化された Single Page Application (SPA) の登録および構成のプロセスについて説明されます。 詳細: OAuth を使用するクロス オリジン リソース共有を使用して単一ページのアプリケーションを Dataverse に接続する

前提条件

このクイック スタートの目的

このクイック スタートを完了すると、シンプルな SPA アプリケーションを実行できるようになり、ユーザーが Dataverse からのデータを認証して取得できる機能が提供されます。

アプリケーションをデバッグする場合、最初にログインボタンのみが表示されます。

  • ログイン をクリックして、資格情報を入力するためのポップアップが開きます。

  • 資格情報を入力した後は、ログイン ボタンが非表示となり、ログアウト ボタンと 取引先企業の取得 ボタンが表示されるようになります。 また、ユーザー アカウントからの情報を使用した挨拶も表示されるようになります。

  • 取引先企業の取得 ボタンをクリックし、Dataverse 組織から 10 件の取引先企業レコードの一覧を取得します。 次のスクリーン ショットに示された結果:

    SimpleSPA ページ。

  • 最後に ログアウト ボタンをクリックしてログアウトします。

注意

この SPA アプリケーションは、堅牢な SPA アプリケーションを開発するパターンを表記することを目的としたものではありません。 アプリケーションの登録および構成におけるプロセスにフォーカスを設定することが簡素化されています。

Dataverse Web API エンドポイントの取得

開発者リソースを表示する にある手順を使用して、アクセスできる環境の Web API エンドポイントを識別します。 https://yourorg.api.crm.dynamics.com/api/data/v9.2 のように表示されます。

アプリケーションの登録

  1. 左側のナビゲーションの Power Platform 管理センター から 管理センター を展開し、 Microsoft Entra IDを選択します。

    Power Platform 管理センターの Microsoft Entra ID

    これにより、Microsoft Entra 管理センターが開きます

  2. アプリケーション を展開して、アプリの登録 を選択します。

     Microsoft Entra 管理センターからの Azure アプリの登録

  3. 新規登録 をクリックします。 これにより、アプリケーションを登録する フォームが開きます。

    登録および申請フォーム

  4. アプリケーションの登録 フォームで、名前 を入力します。 このクイックスタートでは、名前 シンプル SPA を使用します。

  5. サポートされているアカウントの種類 では、デフォルトの選択は次のとおりです。
    この組織ディレクトリのみに含まれるアカウント (<テナント名のみ> – シングル テナント) これを変更しないでください。

  6. リダイレクト URI (オプション) には、次のオプションを使用します。

    • プラットフォームを選択: シングルページ アプリケーション (SPA)
    • e.g. https://example.com/auth: http://localhost:5500/index.html
  7. 登録 をクリックします。

  8. 概要 エリアに、Webアプリケーション プロジェクトの作成 の最後のステップで必要になるため、次の値をコピーします。

    • アプリケーション (クライアント) ID
    • ディレクトリ (テナント) ID
  9. API アクセス許可 を選択します。

  10. アクセス許可の追加 をクリックします。

  11. API アクセス権限のリクエスト フライアウトで、Dynamics CRM を選択します。

    • Dynamics CRM が表示されに場合は、Dataverse を探します。 または、自分の組織が使う API タブを選択して、Dataverse を検索します。
  12. user_impersonation の委任されたアクセス許可を選択します。

  13. アクセス許可の追加 をクリックします。

完了したら、構成されたアクセス許可はこのようになります。

Simple SPA アプリ用に構成されたアクセス許可

Live Server Visual Studio Code 拡張をインストールする

Live Server は、Web ページのローカル開発サーバーを簡単に起動できる Visual Studio Code 拡張機能です。

  1. 次の手順を使用して、VS Code マーケットプレースで VS Code 用の Live Server 拡張機能を見つけてインストールします。

  2. Live Server 拡張機能をインストールしたら、これらの変更をデフォルト設定に加えます。

  3. VS Code で歯車のアイコン を選択し、設定 を選択するか、Ctrl+, キーボード ショートカットを使用します。

  4. 検索ウィンドウで、liveServer.settings.host と入力して、既定値を 127.0.0.1 から localhost に変更します。

Web アプリケーション プロジェクトの作成

  1. コンピューターでフォルダーを作成します。 名前は重要ではありませんが、これらの手順では名前を simplespa と付けます。

  2. VS コードを開き、メニューで ファイル > フォルダーを開く を選択します。 simplespa フォルダーを選択します。

  3. index.html と言う名前のフォルダーで新規 HTML ファイルを作成します。 (index.htm ではない)

  4. 以下の内容を index.html ファイルにコピーします。

    <html>
     <head>
       <meta charset="UTF-8">
       <meta http-equiv="X-UA-Compatible" content="IE=edge">
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
       <script>
          const baseUrl = "https://org.api.crm.dynamics.com";      //<= Change this
          const clientId = "11111111-1111-1111-1111-111111111111"; //<= Change this
          const tenantId = "22222222-2222-2222-2222-222222222222"; //<= Change this
          const redirectUrl = "http://localhost:5500/index.html";
          const webAPIEndpoint = baseUrl +"/api/data/v9.2";
    
    
          // Configuration object to be passed to MSAL instance on creation. 
    
          const msalConfig = {
             auth: {       
                clientId: clientId,
                // Full directory URL, in the form of https://login.microsoftonline.com/<tenant-id>
                authority: "https://login.microsoftonline.com/"+tenantId,       
                redirectUri: redirectUrl,
             },
             cache: {
                cacheLocation: "sessionStorage" // This configures where your cache will be stored
             },
             system: {   
                loggerOptions: {   
                   loggerCallback: (level, message, containsPii) => {   
                         if (containsPii) {      
                            return;      
                         }      
                         switch (level) {      
                            case msal.LogLevel.Error:      
                               console.error(message);      
                               return;      
                            case msal.LogLevel.Info:      
                               console.info(message);      
                               return;      
                            case msal.LogLevel.Verbose:      
                               console.debug(message);      
                               return;      
                            case msal.LogLevel.Warning:      
                               console.warn(message);      
                               return;      
                         }   
                   }   
                }   
             }
          };
    
       </script>
          <!-- Latest version of msal-browser.js from CDN as of 2022/09 -->
       <script
             type="text/javascript" 
             src="https://alcdn.msauth.net/browser/2.28.1/js/msal-browser.min.js">
       </script>
       <style>
          body {  
             font-family: 'Segoe UI';  
          }  
    
          table {  
             border-collapse: collapse;  
          }  
    
          td, th {  
             border: 1px solid black;  
          }
    
          #message {  
             color: green;  
          }
    </style>
    </head>
    <body>
    <div>
       <button id="loginButton" onclick="signIn()">Login</button>
       <button id="logoutButton" onclick="signOut()" style="display:none;">Logout</button>
       <button id="getAccountsButton" onclick="getAccounts(writeTable)" style="display:none;">Get Accounts</button>  
       <div id="message"></div>
       <table id="accountsTable" style="display:none;">  
        <thead><tr><th>Name</th><th>City</th></tr></thead>  
        <tbody id="accountsTableBody"></tbody>  
       </table>
    </div>
    <script>
       const loginButton = document.getElementById("loginButton");
       const logoutButton = document.getElementById("logoutButton");
       const getAccountsButton = document.getElementById("getAccountsButton");
       const accountsTable = document.getElementById("accountsTable");
       const accountsTableBody = document.getElementById("accountsTableBody");
       const message = document.getElementById("message");
       // Create the main myMSALObj instance
       const myMSALObj = new msal.PublicClientApplication(msalConfig);
    
       let username = "";
    
       // Sets the username. Called at the end of this script.
       function selectAccount() {
    
          const currentAccounts = myMSALObj.getAllAccounts();
          if (currentAccounts.length === 0) {
             return;
          } else if (currentAccounts.length > 1) {
             // Add choose account code here
             console.warn("Multiple accounts detected.");
          } else if (currentAccounts.length === 1) {
             username = currentAccounts[0].username;
             showWelcomeMessage(username);
          }
       }
    
       // Called by the loginButton
       function signIn() {
          myMSALObj.loginPopup({
             scopes: ["User.Read",baseUrl+"/user_impersonation"] //<= Includes Dataverse scope
             })
             .then(response =>{
                if (response !== null) {
                username = response.account.username;
                showWelcomeMessage(username);
                   } else {
                      selectAccount();
                   }
             })
             .catch(error => {
                   console.error(error);
             });
       }
    
       // Shows greeting and enables logoutButton and getAccountsButton
       // Called from signIn or selectAccount functions
       function showWelcomeMessage(username) {
        message.innerHTML = `Welcome ${username}`;
        loginButton.style.display = "none";
        logoutButton.style.display = "block";
        getAccountsButton.style.display = "block";
       }
    
       // Called by the logoutButton
       function signOut() {
    
          const logoutRequest = {
             account: myMSALObj.getAccountByUsername(username),
             postLogoutRedirectUri: msalConfig.auth.redirectUri,
             mainWindowRedirectUri: msalConfig.auth.redirectUri
          };
    
          myMSALObj.logoutPopup(logoutRequest);
       }
    
       // Provides the access token for a request, opening pop-up if necessary.
       // Used by GetAccounts function
       function getTokenPopup(request) {
    
          request.account = myMSALObj.getAccountByUsername(username);
    
          return myMSALObj.acquireTokenSilent(request)
             .catch(error => {
                   console.warn("Silent token acquisition fails. Acquiring token using popup");
                   if (error instanceof msal.InteractionRequiredAuthError) {
                      // fallback to interaction when silent call fails
                      return myMSALObj.acquireTokenPopup(request)
                         .then(tokenResponse => {
                               console.log(tokenResponse);
                               return tokenResponse;
                         }).catch(error => {
                               console.error(error);
                         });
                   } else {
                      console.warn(error);   
                   }
          });
       }
    
       // Retrieves top 10 account records from Dataverse
       function getAccounts(callback) {
          // Gets the access token
          getTokenPopup({
                scopes: [baseUrl+"/.default"]
             })
             .then(response => {
                getDataverse("accounts?$select=name,address1_city&$top=10", response.accessToken, callback);
             }).catch(error => {
                console.error(error);
             });
       }
    
       /** 
        * Helper function to get data from Dataverse
       * using the authorization bearer token scheme
       * callback is the writeTable function below
       */
       function getDataverse(url, token, callback) {
           const headers = new Headers();
           const bearer = `Bearer ${token}`;
           headers.append("Authorization", bearer);
           // Other Dataverse headers
           headers.append("Accept", "application/json"); 
           headers.append("OData-MaxVersion", "4.0");  
           headers.append("OData-Version", "4.0");  
    
           const options = {
              method: "GET",
              headers: headers
           };
    
         console.log('GET Request made to Dataverse at: ' + new Date().toString());
    
         fetch(webAPIEndpoint+"/"+url, options)
              .then(response => response.json())
              .then(response => callback(response))
              .catch(error => console.log(error));
        }
    
        // Renders the table with data from GetAccounts
        function writeTable(data) {
    
           data.value.forEach(function (account) {
    
               var name = account.name;
               var city = account.address1_city;
    
               var nameCell = document.createElement("td");
               nameCell.textContent = name;
    
               var cityCell = document.createElement("td");
               cityCell.textContent = city;
    
               var row = document.createElement("tr");
    
               row.appendChild(nameCell);
               row.appendChild(cityCell);
    
               accountsTableBody.appendChild(row); 
    
           });
    
           accountsTable.style.display = "block";
           getAccountsButton.style.display = "none";
        }
    
        selectAccount();
      </script>
     </body>
    </html>
    

    注意

    HTML ページの JavaScript コードは、Microsoft Graph に接続する、こちら https://github.com/Azure-Samples/ms-identity-javascript-v2 で公開されているサンプル コードを基にしています。

    主な違いは、アクセス トークン を取得するときに使用されるスコープです。

    ログイン ボタンに次のスコープを使用します。

      // Called by the loginButton
      function signIn() {
         myMSALObj.loginPopup({
            scopes: ["User.Read",baseUrl+"/user_impersonation"]  //<= Includes Dataverse scope
            })
    

    これらのスコープには、Microsoft Graph User.Read 範囲だけでなく、Dataverseuser_impersonation 範囲も両方が含まれます。。 ログイン時にこれらのスコープの両方を含めることにより、最初の同意ダイアログには、アプリケーションで使用されるすべての必要なスコープが含まれます。

    次に、Dataverse 呼び出しに使用されるスコープを指定するときは、/.default または /user_impersonation のどちらでも使用できます。

          // Retrieves top 10 account records from Dataverse
          function getAccounts(callback) {
             // Gets the access token
             getTokenPopup({
                   scopes: [baseUrl+"/.default"]
                })
    

    /user_impersonation 範囲は、委任されたアクセス許可に対してのみ機能するため、ここではこれを使用できます。 /.default は、委任されたアクセス許可とアプリケーションのアクセス許可の両方で機能します。

    ログイン時に baseUrl+"/user_impersonation" 範囲を含めない場合は、ユーザーは アカウントを取得する ボタンを初めてクリックしたときにもう一度同意する必要があります。。

    その他の SPA の例とチュートリアルは、こちらにあります: シングル ページ アプリケーション (SPA) のドキュメント

  5. index.html ページ内で、次の構成変数を見つけ、前の手順 あなたの Dataverse Web API エンドポイントを取得するアプリケーションを登録する で収集した情報を使用して設定します。

    const baseUrl = "https://org.api.crm.dynamics.com";      //<= Change this
    const clientId = "11111111-1111-1111-1111-111111111111"; //<= Change this
    const tenantId = "22222222-2222-2222-2222-222222222222"; //<= Change this
    

アプリのデバッグ

Live Server Visual Studio Code 拡張機能をインストールする で Live Server 拡張機能をインストールしたため、VS Code ツールバーに次のボタンがあります:

  1. ライブ移行 ボタンをクリックすると、新しいブラウザ ウィンドウが開き、http://localhost:5500/index.html が index.htmlページをレンダリングします。

    初めてアプリを実行して ログイン ボタンをクリックすると、次のような同意ダイアログが表示されます。

    要求されているダイアログのアクセス許可

    管理者 の場合は、組織を代表して同意する チェックボックスを使用すると、他のユーザーも リクエストされた権限 ダイアログを使用することなくアプリを実行できるようになります。

  2. 承認 をクリックしてテストを続行して、アプリが このクイック スタートの目的 の説明どおりに動作することを確認します。

トラブルシューティング​​

このクイック スタートでの体験は、ライブ サーバーのポート設定が既定値: 5500 であるかどうかによって異なります。 Live Server が既にインストールされていて、ポート設定を変更した場合は、デフォルト設定またはアプリ登録で設定された URL を変更する必要があります。

liveServer.settings.portワークスペース に設定することもでき、ユーザー 設定を上書きしますので注意してください。

複数の Live Server インスタンスを開くと、ポート設定が 5501 以上に増加する場合があります。 これにより、認証に使用されるコールバックが壊れます。これは、ポートが http://localhost:5500/index.html のようにアプリケーション登録に 'ハードコード' されているためです。

参照

単一ページ アプリケーション (SPI) 説明書
OAuth を使用するクロス オリジン リソース共有を使用して Dataverse の単一ページのアプリケーションへ接続する
クライアント アプリケーション作成

注意

ドキュメントの言語設定についてお聞かせください。 簡単な調査を行います。 (この調査は英語です)

この調査には約 7 分かかります。 個人データは収集されません (プライバシー ステートメント)。