你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
重要
自 2025 年 5 月 1 日起,Azure AD B2C 将不再可供新客户购买。 在我们的常见问题解答中了解详细信息。
本文介绍如何将 Azure Active Directory B2C (Azure AD B2C) 身份验证添加到自己的单页应用程序(SPA)。 了解如何使用 适用于 JavaScript 的Microsoft身份验证库(MSAL.js)创建 SPA 应用程序。
使用本文 在示例 SPA 应用程序中配置身份验证,将示例 SPA 应用替换为你自己的 SPA 应用。
概述
本文使用 Node.js 和 Express 创建基本的 Node.js Web 应用。 Express 是一个最小且灵活的 Node.js Web 应用框架,它为 Web 和移动应用程序提供了一组功能。
MSAL.js身份验证库是一个 Microsoft 提供的库,可简化向 SPA 应用添加身份验证和授权支持。
小窍门
整个 MSAL.js 代码在客户端运行。 可以将 Node.js 和 Express 服务器端代码替换为其他解决方案,例如 .NET Core、Java 和超文本预处理器(PHP)脚本语言。
先决条件
若要查看先决条件和集成说明,请参阅 示例 SPA 应用程序中配置身份验证。
步骤 1:创建 SPA 应用项目
可以使用现有的 SPA 应用项目或创建新的 SPA 应用项目。 若要创建新项目,请执行以下作:
打开命令行界面并创建新目录(例如 myApp)。 此目录将包含应用代码、用户界面和配置文件。
输入创建的目录。
使用
npm init
命令来为你的应用创建package.json
文件。 此命令会提示你输入有关应用的信息(例如,应用的名称和版本,以及初始入口点的名称, index.js 文件)。 运行以下命令并接受默认值:
npm init
步骤 2:安装依赖项
若要安装 Express 包,请在命令行界面中运行以下命令:
npm install express
若要查找应用的静态文件,服务器端代码使用 Path 包。
若要安装 Path 包,请在命令行界面中运行以下命令:
npm install path
步骤 3:配置 Web 服务器
在 myApp 文件夹中,创建一个名为 index.js的文件,其中包含以下代码:
// Initialize express
const express = require('express');
const app = express();
// The port to listen to incoming HTTP requests
const port = 6420;
// Initialize path
const path = require('path');
// Set the front-end folder to serve public assets.
app.use(express.static('App'));
// Set up a route for the index.html
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname + '/index.html'));
});
// Start the server, and listen for HTTP requests
app.listen(port, () => {
console.log(`Listening on http://localhost:${port}`);
});
步骤 4:创建 SPA 用户界面
添加 SPA 应用 index.html
文件。 此文件实现使用 Bootstrap 框架生成的用户界面,并导入用于配置、身份验证和 Web API 调用的脚本文件。
下表中详细介绍了 index.html 文件引用的资源:
参考文献 | 定义 |
---|---|
MSAL.js 库 | MSAL.js 身份验证 JavaScript 库 CDN 路径。 |
Bootstrap 样式表 | 一个免费的前端框架,用于更快、更轻松地进行 Web 开发。 框架包括基于 HTML 的设计模板和基于 CSS 的设计模板。 |
若要呈现 SPA 索引文件,请在 myApp 文件夹中创建一个名为 index.html的文件,其中包含以下 HTML 代码片段:
<!DOCTYPE html>
<html>
<head>
<title>My Azure AD B2C test app</title>
</head>
<body>
<h2>My Azure AD B2C test app</h2>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous" />
<button type="button" id="signIn" class="btn btn-secondary" onclick="signIn()">Sign-in</button>
<button type="button" id="signOut" class="btn btn-success d-none" onclick="signOut()">Sign-out</button>
<h5 id="welcome-div" class="card-header text-center d-none"></h5>
<br />
<!-- Content -->
<div class="card">
<div class="card-body text-center">
<pre id="response" class="card-text"></pre>
<button type="button" id="callApiButton" class="btn btn-primary d-none" onclick="passTokenToApi()">Call API</button>
</div>
</div>
<script src="https://alcdn.msauth.net/browser/2.14.2/js/msal-browser.min.js" integrity="sha384-ggh+EF1aSqm+Y4yvv2n17KpurNcZTeYtUZUvhPziElsstmIEubyEB6AIVpKLuZgr" crossorigin="anonymous"></script>
<!-- Importing app scripts (load order is important) -->
<script type="text/javascript" src="./apiConfig.js"></script>
<script type="text/javascript" src="./policies.js"></script>
<script type="text/javascript" src="./authConfig.js"></script>
<script type="text/javascript" src="./ui.js"></script>
<!-- <script type="text/javascript" src="./authRedirect.js"></script> -->
<!-- uncomment the above line and comment the line below if you would like to use the redirect flow -->
<script type="text/javascript" src="./authRedirect.js"></script>
<script type="text/javascript" src="./api.js"></script>
</body>
</html>
步骤 5:配置身份验证库
配置 MSAL.js 库如何与 Azure AD B2C 集成。 MSAL.js 库使用通用配置对象连接到 Azure AD B2C 租户的身份验证终结点。
若要配置身份验证库,请执行以下作:
在 myApp 文件夹中,创建名为 App 的新文件夹。
在 App 文件夹中,创建名为 authConfig.js的新文件。
将以下 JavaScript 代码添加到 authConfig.js 文件:
const msalConfig = { auth: { clientId: "<Application-ID>", authority: b2cPolicies.authorities.signUpSignIn.authority, knownAuthorities: [b2cPolicies.authorityDomain], redirectUri: "http://localhost:6420", }, cache: { cacheLocation: "localStorage", . storeAuthStateInCookie: false, } }; const loginRequest = { scopes: ["openid", ...apiConfig.b2cScopes], }; const tokenRequest = { scopes: [...apiConfig.b2cScopes], forceRefresh: false };
将
<Application-ID>
替换为你的应用注册应用程序 ID。 有关详细信息,请参阅 在示例 SPA 应用程序中配置身份验证。
小窍门
有关更多 MSAL 对象配置选项,请参阅 “身份验证选项” 一文。
步骤 6:指定 Azure AD B2C 用户流
创建 policies.js 文件,该文件提供有关 Azure AD B2C 环境的信息。 MSAL.js 库使用此信息创建对 Azure AD B2C 的身份验证请求。
若要指定 Azure AD B2C 用户流,请执行以下作:
在 App 文件夹中,创建名为 policies.js的新文件。
将以下代码添加到 policies.js 文件:
const b2cPolicies = { names: { signUpSignIn: "B2C_1_SUSI", editProfile: "B2C_1_EditProfile" }, authorities: { signUpSignIn: { authority: "https://contoso.b2clogin.com/contoso.onmicrosoft.com/Your-B2C-SignInOrSignUp-Policy-Id", }, editProfile: { authority: "https://contoso.b2clogin.com/contoso.onmicrosoft.com/Your-B2C-EditProfile-Policy-Id" } }, authorityDomain: "contoso.b2clogin.com" }
将
B2C_1_SUSI
替换为你的“登录”Azure AD B2C 策略名称。将
B2C_1_EditProfile
替换为你的“编辑配置文件”Azure AD B2C 策略名称。将所有实例
contoso
替换为 Azure AD B2C 租户名称。
步骤 7:使用 MSAL 登录用户
在此步骤中,实现初始化登录流、API 访问令牌获取和注销方法的方法。
有关详细信息,请参阅 使用Microsoft身份验证库(MSAL)登录用户 文章。
若要登录用户,请执行以下作:
在 App 文件夹中,创建名为 authRedirect.js的新文件。
在 authRedirect.js中,复制并粘贴以下代码:
// Create the main myMSALObj instance // configuration parameters are located at authConfig.js const myMSALObj = new msal.PublicClientApplication(msalConfig); let accountId = ""; let idTokenObject = ""; let accessToken = null; myMSALObj.handleRedirectPromise() .then(response => { if (response) { /** * For the purpose of setting an active account for UI update, we want to consider only the auth response resulting * from SUSI flow. "tfp" claim in the id token tells us the policy (NOTE: legacy policies may use "acr" instead of "tfp"). * To learn more about B2C tokens, visit https://learn.microsoft.com/azure/active-directory-b2c/tokens-overview */ if (response.idTokenClaims['tfp'].toUpperCase() === b2cPolicies.names.signUpSignIn.toUpperCase()) { handleResponse(response); } } }) .catch(error => { console.log(error); }); function setAccount(account) { accountId = account.homeAccountId; idTokenObject = account.idTokenClaims; myClaims= JSON.stringify(idTokenObject); welcomeUser(myClaims); } function selectAccount() { /** * See here for more information on account retrieval: * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md */ const currentAccounts = myMSALObj.getAllAccounts(); if (currentAccounts.length < 1) { return; } else if (currentAccounts.length > 1) { /** * Due to the way MSAL caches account objects, the auth response from initiating a user-flow * is cached as a new account, which results in more than one account in the cache. Here we make * sure we are selecting the account with homeAccountId that contains the sign-up/sign-in user-flow, * as this is the default flow the user initially signed-in with. */ const accounts = currentAccounts.filter(account => account.homeAccountId.toUpperCase().includes(b2cPolicies.names.signUpSignIn.toUpperCase()) && account.idTokenClaims.iss.toUpperCase().includes(b2cPolicies.authorityDomain.toUpperCase()) && account.idTokenClaims.aud === msalConfig.auth.clientId ); if (accounts.length > 1) { // localAccountId identifies the entity for which the token asserts information. if (accounts.every(account => account.localAccountId === accounts[0].localAccountId)) { // All accounts belong to the same user setAccount(accounts[0]); } else { // Multiple users detected. Logout all to be safe. signOut(); }; } else if (accounts.length === 1) { setAccount(accounts[0]); } } else if (currentAccounts.length === 1) { setAccount(currentAccounts[0]); } } // in case of page refresh selectAccount(); async function handleResponse(response) { if (response !== null) { setAccount(response.account); } else { selectAccount(); } } function signIn() { myMSALObj.loginRedirect(loginRequest); } function signOut() { const logoutRequest = { postLogoutRedirectUri: msalConfig.auth.redirectUri, }; myMSALObj.logoutRedirect(logoutRequest); } function getTokenRedirect(request) { request.account = myMSALObj.getAccountByHomeId(accountId); return myMSALObj.acquireTokenSilent(request) .then((response) => { // In case the response from B2C server has an empty accessToken field // throw an error to initiate token acquisition if (!response.accessToken || response.accessToken === "") { throw new msal.InteractionRequiredAuthError; } else { console.log("access_token acquired at: " + new Date().toString()); accessToken = response.accessToken; passTokenToApi(); } }).catch(error => { console.log("Silent token acquisition fails. Acquiring token using popup. \n", error); if (error instanceof msal.InteractionRequiredAuthError) { // fallback to interaction when silent call fails return myMSALObj.acquireTokenRedirect(request); } else { console.log(error); } }); } // Acquires and access token and then passes it to the API call function passTokenToApi() { if (!accessToken) { getTokenRedirect(tokenRequest); } else { try { callApi(apiConfig.webApi, accessToken); } catch(error) { console.log(error); } } } function editProfile() { const editProfileRequest = b2cPolicies.authorities.editProfile; editProfileRequest.loginHint = myMSALObj.getAccountByHomeId(accountId).username; myMSALObj.loginRedirect(editProfileRequest); }
步骤 8:配置 Web API 位置和范围
若要允许 SPA 应用调用 Web API,请提供 Web API 终结点位置和用于授权访问 Web API 的范围。
若要配置 Web API 位置和范围,请执行以下作:
在 App 文件夹中,创建名为 apiConfig.js的新文件。
在 apiConfig.js中,复制并粘贴以下代码:
// The current application coordinates were pre-registered in a B2C tenant. const apiConfig = { b2cScopes: ["https://contoso.onmicrosoft.com/tasks/tasks.read"], webApi: "https://mydomain.azurewebsites.net/tasks" };
将
contoso
替换为你的租户名称。 如 “配置范围 ”一文中所述,可以找到所需的范围名称。将
webApi
的值替换为你的 Web API 终结点位置。
步骤 9:调用 Web API
定义对 API 终结点的 HTTP 请求。 HTTP 请求配置为将使用 MSAL.js 获取的访问令牌传递到 Authorization
请求中的 HTTP 标头。
以下代码定义对 API 终结点的 HTTP GET
请求,并在 HTTP 标头中 Authorization
传递访问令牌。 API 位置由 webApi
apiConfig.js中的密钥定义。
若要使用获取的令牌调用网络 API,请执行以下操作:
在 App 文件夹中,创建名为 api.js的新文件。
将以下代码添加到 api.js 文件:
function callApi(endpoint, token) { const headers = new Headers(); const bearer = `Bearer ${token}`; headers.append("Authorization", bearer); const options = { method: "GET", headers: headers }; logMessage('Calling web API...'); fetch(endpoint, options) .then(response => response.json()) .then(response => { if (response) { logMessage('Web API responded: ' + response.name); } return response; }).catch(error => { console.error(error); }); }
步骤 10:添加 UI 元素引用
SPA 应用使用 JavaScript 来控制 UI 元素。 例如,它显示登录和注销按钮,并将用户的 ID 令牌声明呈现到屏幕。
若要添加 UI 元素引用,请执行以下作:
在 App 文件夹中,创建名为 ui.js的文件。
将以下代码添加到 ui.js 文件:
// Select DOM elements to work with const signInButton = document.getElementById('signIn'); const signOutButton = document.getElementById('signOut') const titleDiv = document.getElementById('title-div'); const welcomeDiv = document.getElementById('welcome-div'); const tableDiv = document.getElementById('table-div'); const tableBody = document.getElementById('table-body-div'); const editProfileButton = document.getElementById('editProfileButton'); const callApiButton = document.getElementById('callApiButton'); const response = document.getElementById("response"); const label = document.getElementById('label'); function welcomeUser(claims) { welcomeDiv.innerHTML = `Token claims: </br></br> ${claims}!` signInButton.classList.add('d-none'); signOutButton.classList.remove('d-none'); welcomeDiv.classList.remove('d-none'); callApiButton.classList.remove('d-none'); } function logMessage(s) { response.appendChild(document.createTextNode('\n' + s + '\n')); }
步骤 11:运行 SPA 应用程序
在命令行界面中运行以下命令:
npm install
npm ./index.js
- 转到 https://localhost:6420.
- 选择“登录”。
- 完成注册或登录过程。
成功进行身份验证后,分析的 ID 令牌将显示在屏幕上。 选择 Call API
以调用 API 终结点。