适用于:
外部租户(了解详细信息)
本文是一系列的第 2 部分,演示如何在 Node.js Web 应用中添加配置文件编辑逻辑。 在本系列文章的第 1 部分,你设置了应用以进行个人资料编辑。
在本操作指南中,你将了解如何调用 Microsoft Graph API 进行个人资料编辑。
先决条件
- 完成本指南系列第二部分(即设置 Node.js Web 应用程序以进行个人资料编辑)中的步骤。
完成客户端 Web 应用
在本部分中,将添加客户端 Web 应用的标识相关代码。
更新 authConfig.js 文件
更新客户端 Web 应用的 authConfig.js 文件:
在代码编辑器中,打开 App/authConfig.js 文件,然后添加三个新变量,
GRAPH_API_ENDPOINTGRAPH_ME_ENDPOINT以及editProfileScope。 请确保导出三个变量://... const GRAPH_API_ENDPOINT = process.env.GRAPH_API_ENDPOINT || "https://graph.microsoft.com/"; // https://free.blessedness.top/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是 Microsoft Graph API 终结点。
将占位符
{clientId}替换为前面注册的中间层应用(EditProfileService 应用)的应用程序(客户端)ID。
在客户端 Web 应用中获取访问令牌
在代码编辑器中,打开 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 文件
在代码编辑器中,打开 应用/路由/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 应用)。 通过调用中间层应用(EditProfileService 应用),用户必须完成 MFA 质询(如果尚未这样做)。
- 在 updateProfile.hbs UI 中显示用户详细信息。
当用户在
/update或 updateProfile.hbs 中选择“保存”按钮时触发路由。 应用:- 检索应用会话的访问令牌。 你将了解中间层应用(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 clientSecret: process.env.CLIENT_SECRET || "Enter_the_Client_Secret_Here ", // Client secret generated from the app registration in Microsoft Entra admin center }, 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://free.blessedness.top/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,并将其替换为目录(租户)子域。 例如,如果租户主域名是contoso.onmicrosoft.com,请使用contoso。 如果没有租户名称,请了解如何读取租户详细信息。 -
Enter_the_Tenant_ID_Here,并将其替换为租户 ID。 如果没有租户 ID,请了解如何读取租户详细信息。 -
Enter_the_Edit_Profile_Service_Application_Id_Here,并将其替换为之前注册的 EditProfileService 的应用程序(客户端)ID 值。 -
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图形 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 质询。
更改任何个人资料详细信息,然后选择“保存”按钮。