共用方式為


編輯Node.js Web 應用程式中的設定檔

適用於具有灰色 X 符號的白色圓圈。 員工租用戶 內含白色核取記號的綠色圓圈。 外部租用戶 (深入了解)

本文是一系列的第 2 部分,示範如何在 Node.js Web 應用程式中新增配置檔編輯邏輯。 在此 系列的第 1 部分中,您會設定應用程式以進行設定檔編輯。

在本操作指南中,您會瞭解如何呼叫 Microsoft Graph API 以進行設定文件編輯。

必要條件

完成用戶端應用程式

在本節中,您會新增用戶端應用程式的身分識別相關程序代碼。

更新authConfig.js檔案

更新用戶端應用程式authConfig.js檔案:

  1. 在您的程式代碼編輯器中,開啟 [應用程式/authConfig.js 檔案],然後新增三個新變數 GRAPH_API_ENDPOINTGRAPH_ME_ENDPOINTeditProfileScope。 請務必匯出三個變數:

    //...
    const GRAPH_API_ENDPOINT = process.env.GRAPH_API_ENDPOINT || "https://graph.microsoft.com/";
    // https://learn.microsoft.com/graph/api/user-update?tabs=http
    const GRAPH_ME_ENDPOINT = GRAPH_API_ENDPOINT + "v1.0/me";
    const editProfileScope = process.env.EDIT_PROFILE_FOR_CLIENT_WEB_APP || 'api://{clientId}/EditProfileService.ReadWrite';
    
    module.exports = {
        //...
        editProfileScope,
        GRAPH_API_ENDPOINT,
        GRAPH_ME_ENDPOINT,
        //...
    };
    
    • 變數 editProfileScope 代表受 MFA 保護的資源,也就是中介層應用程式 (EditProfileService 應用程式)。

    • GRAPH_ME_ENDPOINT是圖形 API 端點Microsoft。

  2. 將佔位元 {clientId} 元取代為您稍早註冊之仲介應用程式 (EditProfileService 應用程式) 的應用程式 (client) 識別碼。

在用戶端應用程式中取得存取令牌

在您的程式代碼編輯器中,開啟 App/auth/AuthProvider.js 檔案,然後更新 getToken 類別中的 AuthProvider 方法:

    class AuthProvider {
    //...
        getToken(scopes, redirectUri = "http://localhost:3000/") {
            return  async function (req, res, next) {
                const msalInstance = authProvider.getMsalInstance(authProvider.config.msalConfig);
                try {
                    msalInstance.getTokenCache().deserialize(req.session.tokenCache);
    
                    const silentRequest = {
                        account: req.session.account,
                        scopes: scopes,
                    };
                    const tokenResponse = await msalInstance.acquireTokenSilent(silentRequest);
    
                    req.session.tokenCache = msalInstance.getTokenCache().serialize();
                    req.session.accessToken = tokenResponse.accessToken;
                    next();
                } catch (error) {
                    if (error instanceof msal.InteractionRequiredAuthError) {
                        req.session.csrfToken = authProvider.cryptoProvider.createNewGuid();
    
                        const state = authProvider.cryptoProvider.base64Encode(
                            JSON.stringify({
                                redirectTo: redirectUri,
                                csrfToken: req.session.csrfToken,
                            })
                        );
                        
                        const authCodeUrlRequestParams = {
                            state: state,
                            scopes: scopes,
                        };
    
                        const authCodeRequestParams = {
                            state: state,
                            scopes: scopes,
                        };
    
                        authProvider.redirectToAuthCodeUrl(
                            req,
                            res,
                            next,
                            authCodeUrlRequestParams,
                            authCodeRequestParams,
                            msalInstance
                        );
                    }
    
                    next(error);
                }
            };
        }
    }
    //...

方法 getToken 會使用指定的範圍來取得存取令牌。 參數 redirectUri 是應用程式取得存取令牌之後的重新導向 URL。

更新users.js檔案

在您的程式代碼編輯器中 ,開啟 App/routes/users.js 檔案,然後新增下列路由:

    //...
    
    var { fetch } = require("../fetch");
    const { GRAPH_ME_ENDPOINT, editProfileScope } = require('../authConfig');
    //...
    
router.get(
  "/gatedUpdateProfile",
  isAuthenticated,
  authProvider.getToken(["User.Read"]), // check if user is authenticated
  async function (req, res, next) {
    const graphResponse = await fetch(
      GRAPH_ME_ENDPOINT,
      req.session.accessToken,
    );
    if (!graphResponse.id) {
      return res 
        .status(501) 
        .send("Failed to fetch profile data"); 
    }
    res.render("gatedUpdateProfile", {
      profile: graphResponse,
    });
  },
);

router.get(
  "/updateProfile",
  isAuthenticated, // check if user is authenticated
  authProvider.getToken(
    ["User.Read", editProfileScope],
    "http://localhost:3000/users/updateProfile",
  ),
  async function (req, res, next) {
    const graphResponse = await fetch(
      GRAPH_ME_ENDPOINT,
      req.session.accessToken,
    );
    if (!graphResponse.id) {
      return res 
        .status(501) 
        .send("Failed to fetch profile data"); 
    }
    res.render("updateProfile", {
      profile: graphResponse,
    });
  },
);

router.post(
  "/update",
  isAuthenticated,
  authProvider.getToken([editProfileScope]),
  async function (req, res, next) {
    try {
      if (!!req.body) {
        let body = req.body;
        fetch(
          "http://localhost:3001/updateUserInfo",
          req.session.accessToken,
          "POST",
          {
            displayName: body.displayName,
            givenName: body.givenName,
            surname: body.surname,
          },
        )
          .then((response) => {
            if (response.status === 204) {
              return res.redirect("/");
            } else {
              next("Not updated");
            }
          })
          .catch((error) => {
            console.log("error,", error);
          });
      } else {
        throw { error: "empty request" };
      }
    } catch (error) {
      next(error);
    }
  },
);
    //...
  • 當客戶使用者選取 [配置檔編輯] 連結時,您會觸發/gatedUpdateProfile路由。 應用程式:

    1. 取得具有 User.Read 許可權的存取令牌。
    2. 呼叫 Microsoft Graph API 以讀取已登入使用者的配置檔。
    3. 在 gatedUpdateProfile.hbs UI 中顯示使用者詳細數據。
  • 當使用者想要更新其顯示名稱時,您會觸發 /updateProfile 路由,也就是他們選取 [ 編輯配置檔 ] 按鈕。 應用程式:

    1. 使用 editProfileScope 範圍呼叫仲介層應用程式 (EditProfileService app)。 藉由呼叫中介層應用程式 (EditProfileService 應用程式),如果使用者尚未這麼做,就必須完成 MFA 挑戰。
    2. 在 updateProfile.hbs UI 中顯示使用者詳細數據。
  • 當使用者在 gatedUpdateProfile.hbs 或 updateProfile.hbs選取 [儲存] 按鈕時,就會觸發/update路由。 應用程式:

    1. 擷取應用程式會話的存取令牌。 您會瞭解如何在下一節中層應用程式 (EditProfileService 應用程式) 取得存取令牌。
    2. 收集所有使用者詳細數據。
    3. 呼叫 Microsoft Graph API 以更新使用者配置檔。

更新fetch.js檔案

應用程式會使用 App/fetch.js 檔案進行實際的API呼叫。

在您的程式代碼編輯器中,開啟 App/fetch.js 檔案,然後新增 PATCH 作業選項。 更新檔案之後,產生的檔案看起來應該類似下列程序代碼:

var axios = require('axios');
var authProvider = require("./auth/AuthProvider");

/**
 * Makes an Authorization "Bearer" request with the given accessToken to the given endpoint.
 * @param endpoint
 * @param accessToken
 * @param method
 */
const fetch = async (endpoint, accessToken, method = "GET", data = null) => {
    const options = {
        headers: {
            Authorization: `Bearer ${accessToken}`,
        },
    };
    console.log(`request made to ${endpoint} at: ` + new Date().toString());

    switch (method) {
        case 'GET':
            const response = await axios.get(endpoint, options);
            return await response.data;
        case 'POST':
            return await axios.post(endpoint, data, options);
        case 'DELETE':
            return await axios.delete(endpoint + `/${data}`, options);
        case 'PATCH': 
            return await axios.patch(endpoint, ReqBody = data, options);
        default:
            return null;
    }
};

module.exports = { fetch };

完成中介層應用程式

在本節中,您會新增中介層應用程式的身分識別相關程序代碼(EditProfileService 應用程式)。

  1. 在您的程式代碼編輯器中,開啟 Api/authConfig.js 檔案,然後新增下列程式代碼:

    require("dotenv").config({ path: ".env.dev" });
    
    const TENANT_SUBDOMAIN =
      process.env.TENANT_SUBDOMAIN || "Enter_the_Tenant_Subdomain_Here";
    const TENANT_ID = process.env.TENANT_ID || "Enter_the_Tenant_ID_Here";
    const REDIRECT_URI =
      process.env.REDIRECT_URI || "http://localhost:3000/auth/redirect";
    const POST_LOGOUT_REDIRECT_URI =
      process.env.POST_LOGOUT_REDIRECT_URI || "http://localhost:3000";
    
    /**
     * Configuration object to be passed to MSAL instance on creation.
     * For a full list of MSAL Node configuration parameters, visit:
     * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/configuration.md
     */
    const msalConfig = {
      auth: {
        clientId:
          process.env.CLIENT_ID ||
          "Enter_the_Edit_Profile_Service_Application_Id_Here", // 'Application (client) ID' of the Edit_Profile Service App registration in Microsoft Entra admin center - this value is a GUID
        authority:
          process.env.AUTHORITY || `https://${TENANT_SUBDOMAIN}.ciamlogin.com/`, // Replace the placeholder with your external tenant name
      },
      system: {
        loggerOptions: {
          loggerCallback(loglevel, message, containsPii) {
            console.log(message);
          },
          piiLoggingEnabled: false,
          logLevel: "Info",
        },
      },
    };
    
    const GRAPH_API_ENDPOINT = process.env.GRAPH_API_ENDPOINT || "graph_end_point";
    // Refers to the user that is single user singed in.
    // https://learn.microsoft.com/en-us/graph/api/user-update?tabs=http
    const GRAPH_ME_ENDPOINT = GRAPH_API_ENDPOINT + "v1.0/me";
    
    module.exports = {
      msalConfig,
      REDIRECT_URI,
      POST_LOGOUT_REDIRECT_URI,
      TENANT_SUBDOMAIN,
      GRAPH_API_ENDPOINT,
      GRAPH_ME_ENDPOINT,
      TENANT_ID,
    };
    

    尋找預留位置:

    • Enter_the_Tenant_Subdomain_Here 並將它取代為 Directory (tenant) 子域。 例如,如果您的租用戶主要網域是 contoso.onmicrosoft.com,請使用 contoso。 如果您沒有租用戶名稱,請了解如何讀取租用戶詳細資料
    • Enter_the_Tenant_ID_Here 並將它取代為租用戶標識碼。 如果您沒有租使用者標識碼,請瞭解如何 閱讀您的租使用者詳細數據
    • Enter_the_Edit_Profile_Service_Application_Id_Here並將它取代為您稍早註冊之 EditProfileService 的應用程式(用戶端)標識碼值。
    • Enter_the_Client_Secret_Here 並將它取代為您稍早複製的 EditProfileService 應用程式秘密 值。
    • graph_end_point,並將它取代為 Microsoft Graph API 端點,其為 https://graph.microsoft.com/
  2. 在您的程式代碼編輯器中,開啟 Api/fetch.js 檔案,然後從 Api/fetch.js 檔案貼上程式碼。 函 fetch 式會使用存取令牌和資源端點來進行實際的 API 呼叫。

  3. 在您的程式代碼編輯器中,開啟 Api/index.js 檔案,然後從 Api/index.js 檔案貼上程式代碼。

使用 acquireTokenOnBehalfOf 取得存取令牌

在 Api/index.js 檔案中,仲介層應用程式 (EditProfileService 應用程式) 會使用 acquireTokenOnBehalfOf 函式來取得存取令牌,該函式會用來代表該使用者更新配置檔。

async function getAccessToken(tokenRequest) {
  try {
    const response = await cca.acquireTokenOnBehalfOf(tokenRequest);
    return response.accessToken;
  } catch (error) {
    console.error("Error acquiring token:", error);
    throw error;
  }
}

參數 tokenRequest 的定義如下列程式代碼所示:

    const tokenRequest = {
      oboAssertion: req.headers.authorization.replace("Bearer ", ""),
      authority: `https://${TENANT_SUBDOMAIN}.ciamlogin.com/${TENANT_ID}`,
      scopes: ["User.ReadWrite"],
      correlationId: `${uuidv4()}`,
    };

在相同的檔案中, API/index.js中層應用程式 (EditProfileService 應用程式) 會呼叫 Microsoft Graph API 來更新使用者的配置檔:

   let accessToken = await getAccessToken(tokenRequest);
    fetch(GRAPH_ME_ENDPOINT, accessToken, "PATCH", req.body)
      .then((response) => {
        if (response.status === 204) {
          res.status(response.status);
          res.json({ message: "Success" });
        } else {
          res.status(502);
          res.json({ message: "Failed, " + response.body });
        }
      })
      .catch((error) => {
        res.status(502);
        res.json({ message: "Failed, " + error });
      });

測試您的應用程式

若要測試您的應用程式,請使用下列步驟:

  1. 若要執行用戶端應用程式,請形成終端機視窗,流覽至 [應用程式 ] 目錄,然後執行下列命令:

    npm start
    
  2. 若要執行用戶端應用程式,請形成終端機視窗,流覽至 API 目錄,然後執行下列命令:

    npm start
    
  3. 開啟瀏覽器,然後移至 http://localhost:3000. 如果您遇到 SSL 憑證錯誤,請建立檔案 .env ,然後新增下列設定:

    # Use this variable only in the development environment. 
    # Remove the variable when you move the app to the production environment.
    NODE_TLS_REJECT_UNAUTHORIZED='0'
    
  4. 選取 [登入] 按鈕,然後進行登入。

  5. 在登入頁面上,輸入您的 [電子郵件地址]、選取 [下一步]、輸入您的 [密碼],然後選取 [登入]。 如果您沒有帳戶,請選取 [沒有帳戶?建立一個] 連結,以啟動註冊流程。

  6. 若要更新設定檔,請選取 [ 配置檔編輯] 連結。 您會看到類似下列螢幕擷取畫面的頁面:

    使用者更新設定檔的螢幕擷取畫面。

  7. 若要編輯設定檔,請選取 [ 編輯設定檔] 按鈕。 如果您尚未這麼做,應用程式會提示您完成 MFA 挑戰。

  8. 變更任何配置檔詳細數據,然後選取 [ 儲存] 按鈕。