編輯Node.js Web 應用程式中的設定檔
適用於: 員工租用戶 外部租用戶 (深入了解)
本文是一系列的第 2 部分,示範如何在 Node.js Web 應用程式中新增配置檔編輯邏輯。 在此 系列的第 1 部分中,您會設定應用程式以進行設定檔編輯。
在本操作指南中,您會瞭解如何呼叫 Microsoft Graph API 以進行設定文件編輯。
必要條件
- 完成本指南系列第二部分中的步驟, 設定Node.js Web 應用程式以進行配置檔編輯。
完成用戶端應用程式
在本節中,您會新增用戶端應用程式的身分識別相關程序代碼。
更新authConfig.js檔案
更新用戶端應用程式authConfig.js檔案:
在您的程式代碼編輯器中,開啟 [應用程式/authConfig.js 檔案],然後新增三個新變數
GRAPH_API_ENDPOINT
、GRAPH_ME_ENDPOINT
和editProfileScope
。 請務必匯出三個變數://... 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。
將佔位元
{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
路由。 應用程式:- 取得具有 User.Read 許可權的存取令牌。
- 呼叫 Microsoft Graph API 以讀取已登入使用者的配置檔。
- 在 gatedUpdateProfile.hbs UI 中顯示使用者詳細數據。
當使用者想要更新其顯示名稱時,您會觸發
/updateProfile
路由,也就是他們選取 [ 編輯配置檔 ] 按鈕。 應用程式:- 使用 editProfileScope 範圍呼叫仲介層應用程式 (EditProfileService app)。 藉由呼叫中介層應用程式 (EditProfileService 應用程式),如果使用者尚未這麼做,就必須完成 MFA 挑戰。
- 在 updateProfile.hbs UI 中顯示使用者詳細數據。
當使用者在 gatedUpdateProfile.hbs 或 updateProfile.hbs 中選取 [儲存] 按鈕時,就會觸發
/update
路由。 應用程式:- 擷取應用程式會話的存取令牌。 您會瞭解如何在下一節中層應用程式 (EditProfileService 應用程式) 取得存取令牌。
- 收集所有使用者詳細數據。
- 呼叫 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 應用程式)。
在您的程式代碼編輯器中,開啟 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/
。
在您的程式代碼編輯器中,開啟 Api/fetch.js 檔案,然後從 Api/fetch.js 檔案貼上程式碼。 函
fetch
式會使用存取令牌和資源端點來進行實際的 API 呼叫。在您的程式代碼編輯器中,開啟 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 });
});
測試您的應用程式
若要測試您的應用程式,請使用下列步驟:
若要執行用戶端應用程式,請形成終端機視窗,流覽至 [應用程式 ] 目錄,然後執行下列命令:
npm start
若要執行用戶端應用程式,請形成終端機視窗,流覽至 API 目錄,然後執行下列命令:
npm start
開啟瀏覽器,然後移至 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'
選取 [登入] 按鈕,然後進行登入。
在登入頁面上,輸入您的 [電子郵件地址]、選取 [下一步]、輸入您的 [密碼],然後選取 [登入]。 如果您沒有帳戶,請選取 [沒有帳戶?建立一個] 連結,以啟動註冊流程。
若要更新設定檔,請選取 [ 配置檔編輯] 連結。 您會看到類似下列螢幕擷取畫面的頁面:
若要編輯設定檔,請選取 [ 編輯設定檔] 按鈕。 如果您尚未這麼做,應用程式會提示您完成 MFA 挑戰。
變更任何配置檔詳細數據,然後選取 [ 儲存] 按鈕。