使用 Azure Active Directory B2C 在你自己的 Node Web 应用程序中启用身份验证
本文将介绍如何在你自己的 Node.js Web 应用程序中添加 Azure Active Directory B2C (Azure AD B2C) 身份验证。 你需要允许用户使用 Azure AD B2C 用户流登录、注销、更新个人资料和重置密码。 本文使用适用于 Node 的 Microsoft 身份验证库 (MSAL) 来简化向 Node Web 应用程序添加身份验证的过程。
本文的目的是将你在使用 Azure AD B2C 在示例 Node.js Web 应用程序中配置身份验证中使用的示例应用程序替换为你自己的 Node.js Web 应用程序。
本文使用 Node.js 和 Express 创建基本的 Node.js Web 应用。 该应用程序的视图使用 Handlebars。
先决条件
- 完成使用 Azure AD B2C 在示例 Node.js Web 应用程序中配置身份验证中的步骤。 需创建 Azure AD B2C 用户流,并在 Azure 门户中注册 Web 应用程序。
步骤 1:创建 Node 项目
创建一个用于托管 node 应用程序的文件夹,例如 active-directory-b2c-msal-node-sign-in-sign-out-webapp
。
在终端中,将目录更改为 Node 应用文件夹,例如
cd active-directory-b2c-msal-node-sign-in-sign-out-webapp
,然后运行npm init -y
。 此命令将为 Node.js 项目创建一个默认package.json
文件。在终端中,运行
npm install express
。 此命令安装 Express 框架。创建更多文件夹和文件以实现以下项目结构:
active-directory-b2c-msal-node-sign-in-sign-out-webapp/ ├── index.js └── package.json └── .env └── views/ └── layouts/ └── main.hbs └── signin.hbs
views
文件夹包含应用 UI 的 Handlebars 文件。
步骤 2:安装应用依赖项
在终端中,通过运行以下命令来安装 dotenv
、express-handlebars
、express-session
和 @azure/msal-node
包:
npm install dotenv
npm install express-handlebars
npm install express-session
npm install @azure/msal-node
步骤 3:生成应用 UI 组件
在 main.hbs
文件中添加以下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Tutorial | Authenticate users with MSAL for B2C</title>
<!-- adding Bootstrap 4 for UI components -->
<!-- CSS only -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="SHORTCUT ICON" href="https://c.s-microsoft.com/favicon.ico?v2" type="image/x-icon">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<a class="navbar-brand" href="/">Microsoft Identity Platform</a>
{{#if showSignInButton}}
<div class="ml-auto">
<a type="button" id="SignIn" class="btn btn-secondary" href="/signin" aria-haspopup="true" aria-expanded="false">
Sign in
</a>
</div>
{{else}}
<div class="ml-auto">
<a type="button" id="EditProfile" class="btn btn-warning" href="/profile" aria-haspopup="true" aria-expanded="false">
Edit profile
</a>
<a type="button" id="PasswordReset" class="btn btn-warning" href="/password" aria-haspopup="true" aria-expanded="false">
Reset password
</a>
</div>
<p class="navbar-brand d-flex ms-auto">Hi {{givenName}}</p>
<a class="navbar-brand d-flex ms-auto" href="/signout">Sign out</a>
{{/if}}
</nav>
<br>
<h5 class="card-header text-center">MSAL Node Confidential Client application with Auth Code Flow</h5>
<br>
<div class="row" style="margin:auto" >
{{{body}}}
</div>
<br>
<br>
</body>
</html>
main.hbs
文件位于 layout
文件夹中。 它应包含整个应用程序所需的任何 HTML 代码。 在不同视图中有变化的任何 UI(例如 signin.hbs
中的)放置在显示为 {{{body}}}
的占位符中。
main.hbs
文件可实现使用 Bootstrap 5 CSS 框架生成的 UI。 你在登录时会看到“编辑密码”、“重置密码”和“注销”UI 组件(按钮)。 你在注销后会看到“登录”。此行为由应用服务器发送的 showSignInButton
布尔变量控制。
在 signin.hbs
文件中添加以下代码:
<div class="col-md-3" style="margin:auto">
<div class="card text-center">
<div class="card-body">
{{#if showSignInButton}}
<h5 class="card-title">Please sign-in to acquire an ID token</h5>
{{else}}
<h5 class="card-title">You have signed in</h5>
{{/if}}
</div>
<div class="card-body">
{{#if message}}
<h5 class="card-title text-danger">{{message}}</h5>
{{/if}}
</div>
</div>
</div>
步骤 4:配置 Web 服务器和 MSAL 客户端
- 在
.env
文件中添加以下代码并更新它,如配置示例 Web 应用中解释的那样。
#HTTP port
SERVER_PORT=3000
#web apps client ID
APP_CLIENT_ID=<You app client ID here>
#session secret
SESSION_SECRET=sessionSecretHere
#web app client secret
APP_CLIENT_SECRET=<Your app client secret here>
#B2C sign up and sign in user flow/policy authority
SIGN_UP_SIGN_IN_POLICY_AUTHORITY=https://<your-tenant-name>.b2clogin.cn/<your-tenant-name>.partner.onmschina.cn/<sign-in-sign-up-user-flow-name>
#B2C password reset user flow/policy authority
RESET_PASSWORD_POLICY_AUTHORITY=https://<your-tenant-name>.b2clogin.cn/<your-tenant-name>.partner.onmschina.cn/<reset-password-user-flow-name>
#B2C edit profile user flow/policy authority
EDIT_PROFILE_POLICY_AUTHORITY=https://<your-tenant-name>.b2clogin.cn/<your-tenant-name>.partner.onmschina.cn/<profile-edit-user-flow-name>
#B2C authority domain
AUTHORITY_DOMAIN=https://<your-tenant-name>.b2clogin.cn
#client redirect url
APP_REDIRECT_URI=http://localhost:3000/redirect
#Logout endpoint
LOGOUT_ENDPOINT=https://<your-tenant-name>.b2clogin.cn/<your-tenant-name>.partner.onmschina.cn/<sign-in-sign-up-user-flow-name>/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000
- 在
index.js
文件中,添加以下代码以使用应用依赖项:
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
//<ms_docref_use_app_dependencies>
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const {engine} = require('express-handlebars');
const msal = require('@azure/msal-node');
//</ms_docref_use_app_dependencies>
//<ms_docref_configure_msal>
/**
* Confidential Client Application Configuration
*/
const confidentialClientConfig = {
auth: {
clientId: process.env.APP_CLIENT_ID,
authority: process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY,
clientSecret: process.env.APP_CLIENT_SECRET,
knownAuthorities: [process.env.AUTHORITY_DOMAIN], //This must be an array
redirectUri: process.env.APP_REDIRECT_URI,
validateAuthority: false
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
// Initialize MSAL Node
const confidentialClientApplication = new msal.ConfidentialClientApplication(confidentialClientConfig);
//</ms_docref_configure_msal>
//<ms_docref_global_variable>
/**
* The MSAL.js library allows you to pass your custom state as state parameter in the Request object
* By default, MSAL.js passes a randomly generated unique state parameter value in the authentication requests.
* The state parameter can also be used to encode information of the app's state before redirect.
* You can pass the user's state in the app, such as the page or view they were on, as input to this parameter.
* For more information, visit: /active-directory/develop/msal-js-pass-custom-state-authentication-request
* In this scenario, the states also serve to show the action that was requested of B2C since only one redirect URL is possible.
*/
const APP_STATES = {
LOGIN: 'login',
LOGOUT: 'logout',
PASSWORD_RESET: 'password_reset',
EDIT_PROFILE : 'update_profile'
}
/**
* Request Configuration
* We manipulate these two request objects below
* to acquire a token with the appropriate claims.
*/
const authCodeRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
const tokenRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
/**
* Using express-session middleware. Be sure to familiarize yourself with available options
* and set them as desired. Visit: https://www.npmjs.com/package/express-session
*/
const sessionConfig = {
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // set this to true on production
}
}
//</ms_docref_global_variable>
//<ms_docref_view_tepmplate_engine>
//Create an express instance
const app = express();
//Set handlebars as your view engine
app.engine('.hbs', engine({extname: '.hbs'}));
app.set('view engine', '.hbs');
app.set("views", "./views");
//usse session configuration
app.use(session(sessionConfig));
//</ms_docref_view_tepmplate_engine>
//<ms_docref_authorization_code_url>
/**
* This method is used to generate an auth code request
* @param {string} authority: the authority to request the auth code from
* @param {array} scopes: scopes to request the auth code for
* @param {string} state: state of the application
* @param {Object} res: express middleware response object
*/
const getAuthCode = (authority, scopes, state, res) => {
// prepare the request
console.log("Fetching Authorization code")
authCodeRequest.authority = authority;
authCodeRequest.scopes = scopes;
authCodeRequest.state = state;
//Each time you fetch Authorization code, update the relevant authority in the tokenRequest configuration
tokenRequest.authority = authority;
// request an authorization code to exchange for a token
return confidentialClientApplication.getAuthCodeUrl(authCodeRequest)
.then((response) => {
console.log("\nAuthCodeURL: \n" + response);
//redirect to the auth code URL/send code to
res.redirect(response);
})
.catch((error) => {
res.status(500).send(error);
});
}
//</ms_docref_authorization_code_url>
//<ms_docref_app_endpoints>
app.get('/', (req, res) => {
res.render('signin', { showSignInButton: true });
});
app.get('/signin',(req, res)=>{
//Initiate a Auth Code Flow >> for sign in
//no scopes passed. openid, profile and offline_access will be used by default.
getAuthCode(process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY, [], APP_STATES.LOGIN, res);
});
/**
* Change password end point
*/
app.get('/password',(req, res)=>{
getAuthCode(process.env.RESET_PASSWORD_POLICY_AUTHORITY, [], APP_STATES.PASSWORD_RESET, res);
});
/**
* Edit profile end point
*/
app.get('/profile',(req, res)=>{
getAuthCode(process.env.EDIT_PROFILE_POLICY_AUTHORITY, [], APP_STATES.EDIT_PROFILE, res);
});
/**
* Sign out end point
*/
app.get('/signout',async (req, res)=>{
logoutUri = process.env.LOGOUT_ENDPOINT;
req.session.destroy(() => {
//When session destruction succeeds, notify B2C service using the logout uri.
res.redirect(logoutUri);
});
});
app.get('/redirect',(req, res)=>{
//determine the reason why the request was sent by checking the state
if (req.query.state === APP_STATES.LOGIN) {
//prepare the request for authentication
tokenRequest.code = req.query.code;
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\nAuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
console.log("\nErrorAtLogin: \n" + error);
});
}else if (req.query.state === APP_STATES.PASSWORD_RESET) {
//If the query string has a error param
if (req.query.error) {
//and if the error_description contains AADB2C90091 error code
//Means user selected the Cancel button on the password reset experience
if (JSON.stringify(req.query.error_description).includes('AADB2C90091')) {
//Send the user home with some message
//But always check if your session still exists
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name, message: 'User has cancelled the operation'});
}
}else{
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name});
}
}else if (req.query.state === APP_STATES.EDIT_PROFILE){
tokenRequest.scopes = [];
tokenRequest.code = req.query.code;
//Request token with claims, including the name that was updated.
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\AuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
//Handle error
});
}else{
res.status(500).send('We do not recognize this response!');
}
});
//</ms_docref_app_endpoints>
//start app server to listen on set port
//<ms_docref_start_node_server>
app.listen(process.env.SERVER_PORT, () => {
console.log(`Msal Node Auth Code Sample app listening on port !` + process.env.SERVER_PORT);
});
//</ms_docref_start_node_server>
- 在
index.js
文件中,添加以下代码以配置身份验证库:
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
//<ms_docref_use_app_dependencies>
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const {engine} = require('express-handlebars');
const msal = require('@azure/msal-node');
//</ms_docref_use_app_dependencies>
//<ms_docref_configure_msal>
/**
* Confidential Client Application Configuration
*/
const confidentialClientConfig = {
auth: {
clientId: process.env.APP_CLIENT_ID,
authority: process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY,
clientSecret: process.env.APP_CLIENT_SECRET,
knownAuthorities: [process.env.AUTHORITY_DOMAIN], //This must be an array
redirectUri: process.env.APP_REDIRECT_URI,
validateAuthority: false
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
// Initialize MSAL Node
const confidentialClientApplication = new msal.ConfidentialClientApplication(confidentialClientConfig);
//</ms_docref_configure_msal>
//<ms_docref_global_variable>
/**
* The MSAL.js library allows you to pass your custom state as state parameter in the Request object
* By default, MSAL.js passes a randomly generated unique state parameter value in the authentication requests.
* The state parameter can also be used to encode information of the app's state before redirect.
* You can pass the user's state in the app, such as the page or view they were on, as input to this parameter.
* For more information, visit: /active-directory/develop/msal-js-pass-custom-state-authentication-request
* In this scenario, the states also serve to show the action that was requested of B2C since only one redirect URL is possible.
*/
const APP_STATES = {
LOGIN: 'login',
LOGOUT: 'logout',
PASSWORD_RESET: 'password_reset',
EDIT_PROFILE : 'update_profile'
}
/**
* Request Configuration
* We manipulate these two request objects below
* to acquire a token with the appropriate claims.
*/
const authCodeRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
const tokenRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
/**
* Using express-session middleware. Be sure to familiarize yourself with available options
* and set them as desired. Visit: https://www.npmjs.com/package/express-session
*/
const sessionConfig = {
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // set this to true on production
}
}
//</ms_docref_global_variable>
//<ms_docref_view_tepmplate_engine>
//Create an express instance
const app = express();
//Set handlebars as your view engine
app.engine('.hbs', engine({extname: '.hbs'}));
app.set('view engine', '.hbs');
app.set("views", "./views");
//usse session configuration
app.use(session(sessionConfig));
//</ms_docref_view_tepmplate_engine>
//<ms_docref_authorization_code_url>
/**
* This method is used to generate an auth code request
* @param {string} authority: the authority to request the auth code from
* @param {array} scopes: scopes to request the auth code for
* @param {string} state: state of the application
* @param {Object} res: express middleware response object
*/
const getAuthCode = (authority, scopes, state, res) => {
// prepare the request
console.log("Fetching Authorization code")
authCodeRequest.authority = authority;
authCodeRequest.scopes = scopes;
authCodeRequest.state = state;
//Each time you fetch Authorization code, update the relevant authority in the tokenRequest configuration
tokenRequest.authority = authority;
// request an authorization code to exchange for a token
return confidentialClientApplication.getAuthCodeUrl(authCodeRequest)
.then((response) => {
console.log("\nAuthCodeURL: \n" + response);
//redirect to the auth code URL/send code to
res.redirect(response);
})
.catch((error) => {
res.status(500).send(error);
});
}
//</ms_docref_authorization_code_url>
//<ms_docref_app_endpoints>
app.get('/', (req, res) => {
res.render('signin', { showSignInButton: true });
});
app.get('/signin',(req, res)=>{
//Initiate a Auth Code Flow >> for sign in
//no scopes passed. openid, profile and offline_access will be used by default.
getAuthCode(process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY, [], APP_STATES.LOGIN, res);
});
/**
* Change password end point
*/
app.get('/password',(req, res)=>{
getAuthCode(process.env.RESET_PASSWORD_POLICY_AUTHORITY, [], APP_STATES.PASSWORD_RESET, res);
});
/**
* Edit profile end point
*/
app.get('/profile',(req, res)=>{
getAuthCode(process.env.EDIT_PROFILE_POLICY_AUTHORITY, [], APP_STATES.EDIT_PROFILE, res);
});
/**
* Sign out end point
*/
app.get('/signout',async (req, res)=>{
logoutUri = process.env.LOGOUT_ENDPOINT;
req.session.destroy(() => {
//When session destruction succeeds, notify B2C service using the logout uri.
res.redirect(logoutUri);
});
});
app.get('/redirect',(req, res)=>{
//determine the reason why the request was sent by checking the state
if (req.query.state === APP_STATES.LOGIN) {
//prepare the request for authentication
tokenRequest.code = req.query.code;
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\nAuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
console.log("\nErrorAtLogin: \n" + error);
});
}else if (req.query.state === APP_STATES.PASSWORD_RESET) {
//If the query string has a error param
if (req.query.error) {
//and if the error_description contains AADB2C90091 error code
//Means user selected the Cancel button on the password reset experience
if (JSON.stringify(req.query.error_description).includes('AADB2C90091')) {
//Send the user home with some message
//But always check if your session still exists
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name, message: 'User has cancelled the operation'});
}
}else{
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name});
}
}else if (req.query.state === APP_STATES.EDIT_PROFILE){
tokenRequest.scopes = [];
tokenRequest.code = req.query.code;
//Request token with claims, including the name that was updated.
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\AuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
//Handle error
});
}else{
res.status(500).send('We do not recognize this response!');
}
});
//</ms_docref_app_endpoints>
//start app server to listen on set port
//<ms_docref_start_node_server>
app.listen(process.env.SERVER_PORT, () => {
console.log(`Msal Node Auth Code Sample app listening on port !` + process.env.SERVER_PORT);
});
//</ms_docref_start_node_server>
confidentialClientConfig
是用于连接到 Azure AD B2C 租户的身份验证终结点的 MSAL 配置对象。
- 若要在
index.js
文件中添加更多全局变量,请添加以下代码:
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
//<ms_docref_use_app_dependencies>
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const {engine} = require('express-handlebars');
const msal = require('@azure/msal-node');
//</ms_docref_use_app_dependencies>
//<ms_docref_configure_msal>
/**
* Confidential Client Application Configuration
*/
const confidentialClientConfig = {
auth: {
clientId: process.env.APP_CLIENT_ID,
authority: process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY,
clientSecret: process.env.APP_CLIENT_SECRET,
knownAuthorities: [process.env.AUTHORITY_DOMAIN], //This must be an array
redirectUri: process.env.APP_REDIRECT_URI,
validateAuthority: false
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
// Initialize MSAL Node
const confidentialClientApplication = new msal.ConfidentialClientApplication(confidentialClientConfig);
//</ms_docref_configure_msal>
//<ms_docref_global_variable>
/**
* The MSAL.js library allows you to pass your custom state as state parameter in the Request object
* By default, MSAL.js passes a randomly generated unique state parameter value in the authentication requests.
* The state parameter can also be used to encode information of the app's state before redirect.
* You can pass the user's state in the app, such as the page or view they were on, as input to this parameter.
* For more information, visit: /active-directory/develop/msal-js-pass-custom-state-authentication-request
* In this scenario, the states also serve to show the action that was requested of B2C since only one redirect URL is possible.
*/
const APP_STATES = {
LOGIN: 'login',
LOGOUT: 'logout',
PASSWORD_RESET: 'password_reset',
EDIT_PROFILE : 'update_profile'
}
/**
* Request Configuration
* We manipulate these two request objects below
* to acquire a token with the appropriate claims.
*/
const authCodeRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
const tokenRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
/**
* Using express-session middleware. Be sure to familiarize yourself with available options
* and set them as desired. Visit: https://www.npmjs.com/package/express-session
*/
const sessionConfig = {
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // set this to true on production
}
}
//</ms_docref_global_variable>
//<ms_docref_view_tepmplate_engine>
//Create an express instance
const app = express();
//Set handlebars as your view engine
app.engine('.hbs', engine({extname: '.hbs'}));
app.set('view engine', '.hbs');
app.set("views", "./views");
//usse session configuration
app.use(session(sessionConfig));
//</ms_docref_view_tepmplate_engine>
//<ms_docref_authorization_code_url>
/**
* This method is used to generate an auth code request
* @param {string} authority: the authority to request the auth code from
* @param {array} scopes: scopes to request the auth code for
* @param {string} state: state of the application
* @param {Object} res: express middleware response object
*/
const getAuthCode = (authority, scopes, state, res) => {
// prepare the request
console.log("Fetching Authorization code")
authCodeRequest.authority = authority;
authCodeRequest.scopes = scopes;
authCodeRequest.state = state;
//Each time you fetch Authorization code, update the relevant authority in the tokenRequest configuration
tokenRequest.authority = authority;
// request an authorization code to exchange for a token
return confidentialClientApplication.getAuthCodeUrl(authCodeRequest)
.then((response) => {
console.log("\nAuthCodeURL: \n" + response);
//redirect to the auth code URL/send code to
res.redirect(response);
})
.catch((error) => {
res.status(500).send(error);
});
}
//</ms_docref_authorization_code_url>
//<ms_docref_app_endpoints>
app.get('/', (req, res) => {
res.render('signin', { showSignInButton: true });
});
app.get('/signin',(req, res)=>{
//Initiate a Auth Code Flow >> for sign in
//no scopes passed. openid, profile and offline_access will be used by default.
getAuthCode(process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY, [], APP_STATES.LOGIN, res);
});
/**
* Change password end point
*/
app.get('/password',(req, res)=>{
getAuthCode(process.env.RESET_PASSWORD_POLICY_AUTHORITY, [], APP_STATES.PASSWORD_RESET, res);
});
/**
* Edit profile end point
*/
app.get('/profile',(req, res)=>{
getAuthCode(process.env.EDIT_PROFILE_POLICY_AUTHORITY, [], APP_STATES.EDIT_PROFILE, res);
});
/**
* Sign out end point
*/
app.get('/signout',async (req, res)=>{
logoutUri = process.env.LOGOUT_ENDPOINT;
req.session.destroy(() => {
//When session destruction succeeds, notify B2C service using the logout uri.
res.redirect(logoutUri);
});
});
app.get('/redirect',(req, res)=>{
//determine the reason why the request was sent by checking the state
if (req.query.state === APP_STATES.LOGIN) {
//prepare the request for authentication
tokenRequest.code = req.query.code;
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\nAuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
console.log("\nErrorAtLogin: \n" + error);
});
}else if (req.query.state === APP_STATES.PASSWORD_RESET) {
//If the query string has a error param
if (req.query.error) {
//and if the error_description contains AADB2C90091 error code
//Means user selected the Cancel button on the password reset experience
if (JSON.stringify(req.query.error_description).includes('AADB2C90091')) {
//Send the user home with some message
//But always check if your session still exists
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name, message: 'User has cancelled the operation'});
}
}else{
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name});
}
}else if (req.query.state === APP_STATES.EDIT_PROFILE){
tokenRequest.scopes = [];
tokenRequest.code = req.query.code;
//Request token with claims, including the name that was updated.
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\AuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
//Handle error
});
}else{
res.status(500).send('We do not recognize this response!');
}
});
//</ms_docref_app_endpoints>
//start app server to listen on set port
//<ms_docref_start_node_server>
app.listen(process.env.SERVER_PORT, () => {
console.log(`Msal Node Auth Code Sample app listening on port !` + process.env.SERVER_PORT);
});
//</ms_docref_start_node_server>
APP_STATES
:用于通过标记请求来区分从 Azure AD B2C 收到的响应。 对于发送到 Azure AD B2C 的任意数量的请求,只有一个重定向 URI。authCodeRequest
:用于检索授权代码的配置对象。tokenRequest
:用于通过授权代码获取令牌的配置对象。sessionConfig
:Express 会话的配置对象。若要设置视图模板引擎和 Express 会话配置,请在
index.js
文件中添加以下代码:
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
//<ms_docref_use_app_dependencies>
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const {engine} = require('express-handlebars');
const msal = require('@azure/msal-node');
//</ms_docref_use_app_dependencies>
//<ms_docref_configure_msal>
/**
* Confidential Client Application Configuration
*/
const confidentialClientConfig = {
auth: {
clientId: process.env.APP_CLIENT_ID,
authority: process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY,
clientSecret: process.env.APP_CLIENT_SECRET,
knownAuthorities: [process.env.AUTHORITY_DOMAIN], //This must be an array
redirectUri: process.env.APP_REDIRECT_URI,
validateAuthority: false
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
// Initialize MSAL Node
const confidentialClientApplication = new msal.ConfidentialClientApplication(confidentialClientConfig);
//</ms_docref_configure_msal>
//<ms_docref_global_variable>
/**
* The MSAL.js library allows you to pass your custom state as state parameter in the Request object
* By default, MSAL.js passes a randomly generated unique state parameter value in the authentication requests.
* The state parameter can also be used to encode information of the app's state before redirect.
* You can pass the user's state in the app, such as the page or view they were on, as input to this parameter.
* For more information, visit: /active-directory/develop/msal-js-pass-custom-state-authentication-request
* In this scenario, the states also serve to show the action that was requested of B2C since only one redirect URL is possible.
*/
const APP_STATES = {
LOGIN: 'login',
LOGOUT: 'logout',
PASSWORD_RESET: 'password_reset',
EDIT_PROFILE : 'update_profile'
}
/**
* Request Configuration
* We manipulate these two request objects below
* to acquire a token with the appropriate claims.
*/
const authCodeRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
const tokenRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
/**
* Using express-session middleware. Be sure to familiarize yourself with available options
* and set them as desired. Visit: https://www.npmjs.com/package/express-session
*/
const sessionConfig = {
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // set this to true on production
}
}
//</ms_docref_global_variable>
//<ms_docref_view_tepmplate_engine>
//Create an express instance
const app = express();
//Set handlebars as your view engine
app.engine('.hbs', engine({extname: '.hbs'}));
app.set('view engine', '.hbs');
app.set("views", "./views");
//usse session configuration
app.use(session(sessionConfig));
//</ms_docref_view_tepmplate_engine>
//<ms_docref_authorization_code_url>
/**
* This method is used to generate an auth code request
* @param {string} authority: the authority to request the auth code from
* @param {array} scopes: scopes to request the auth code for
* @param {string} state: state of the application
* @param {Object} res: express middleware response object
*/
const getAuthCode = (authority, scopes, state, res) => {
// prepare the request
console.log("Fetching Authorization code")
authCodeRequest.authority = authority;
authCodeRequest.scopes = scopes;
authCodeRequest.state = state;
//Each time you fetch Authorization code, update the relevant authority in the tokenRequest configuration
tokenRequest.authority = authority;
// request an authorization code to exchange for a token
return confidentialClientApplication.getAuthCodeUrl(authCodeRequest)
.then((response) => {
console.log("\nAuthCodeURL: \n" + response);
//redirect to the auth code URL/send code to
res.redirect(response);
})
.catch((error) => {
res.status(500).send(error);
});
}
//</ms_docref_authorization_code_url>
//<ms_docref_app_endpoints>
app.get('/', (req, res) => {
res.render('signin', { showSignInButton: true });
});
app.get('/signin',(req, res)=>{
//Initiate a Auth Code Flow >> for sign in
//no scopes passed. openid, profile and offline_access will be used by default.
getAuthCode(process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY, [], APP_STATES.LOGIN, res);
});
/**
* Change password end point
*/
app.get('/password',(req, res)=>{
getAuthCode(process.env.RESET_PASSWORD_POLICY_AUTHORITY, [], APP_STATES.PASSWORD_RESET, res);
});
/**
* Edit profile end point
*/
app.get('/profile',(req, res)=>{
getAuthCode(process.env.EDIT_PROFILE_POLICY_AUTHORITY, [], APP_STATES.EDIT_PROFILE, res);
});
/**
* Sign out end point
*/
app.get('/signout',async (req, res)=>{
logoutUri = process.env.LOGOUT_ENDPOINT;
req.session.destroy(() => {
//When session destruction succeeds, notify B2C service using the logout uri.
res.redirect(logoutUri);
});
});
app.get('/redirect',(req, res)=>{
//determine the reason why the request was sent by checking the state
if (req.query.state === APP_STATES.LOGIN) {
//prepare the request for authentication
tokenRequest.code = req.query.code;
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\nAuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
console.log("\nErrorAtLogin: \n" + error);
});
}else if (req.query.state === APP_STATES.PASSWORD_RESET) {
//If the query string has a error param
if (req.query.error) {
//and if the error_description contains AADB2C90091 error code
//Means user selected the Cancel button on the password reset experience
if (JSON.stringify(req.query.error_description).includes('AADB2C90091')) {
//Send the user home with some message
//But always check if your session still exists
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name, message: 'User has cancelled the operation'});
}
}else{
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name});
}
}else if (req.query.state === APP_STATES.EDIT_PROFILE){
tokenRequest.scopes = [];
tokenRequest.code = req.query.code;
//Request token with claims, including the name that was updated.
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\AuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
//Handle error
});
}else{
res.status(500).send('We do not recognize this response!');
}
});
//</ms_docref_app_endpoints>
//start app server to listen on set port
//<ms_docref_start_node_server>
app.listen(process.env.SERVER_PORT, () => {
console.log(`Msal Node Auth Code Sample app listening on port !` + process.env.SERVER_PORT);
});
//</ms_docref_start_node_server>
步骤 5:添加快速路由
在添加应用路由之前,请添加检索授权代码 URL 的逻辑,这是授权代码授予流的第一个分支。 在 index.js
文件中添加以下代码:
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
//<ms_docref_use_app_dependencies>
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const {engine} = require('express-handlebars');
const msal = require('@azure/msal-node');
//</ms_docref_use_app_dependencies>
//<ms_docref_configure_msal>
/**
* Confidential Client Application Configuration
*/
const confidentialClientConfig = {
auth: {
clientId: process.env.APP_CLIENT_ID,
authority: process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY,
clientSecret: process.env.APP_CLIENT_SECRET,
knownAuthorities: [process.env.AUTHORITY_DOMAIN], //This must be an array
redirectUri: process.env.APP_REDIRECT_URI,
validateAuthority: false
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
// Initialize MSAL Node
const confidentialClientApplication = new msal.ConfidentialClientApplication(confidentialClientConfig);
//</ms_docref_configure_msal>
//<ms_docref_global_variable>
/**
* The MSAL.js library allows you to pass your custom state as state parameter in the Request object
* By default, MSAL.js passes a randomly generated unique state parameter value in the authentication requests.
* The state parameter can also be used to encode information of the app's state before redirect.
* You can pass the user's state in the app, such as the page or view they were on, as input to this parameter.
* For more information, visit: /active-directory/develop/msal-js-pass-custom-state-authentication-request
* In this scenario, the states also serve to show the action that was requested of B2C since only one redirect URL is possible.
*/
const APP_STATES = {
LOGIN: 'login',
LOGOUT: 'logout',
PASSWORD_RESET: 'password_reset',
EDIT_PROFILE : 'update_profile'
}
/**
* Request Configuration
* We manipulate these two request objects below
* to acquire a token with the appropriate claims.
*/
const authCodeRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
const tokenRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
/**
* Using express-session middleware. Be sure to familiarize yourself with available options
* and set them as desired. Visit: https://www.npmjs.com/package/express-session
*/
const sessionConfig = {
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // set this to true on production
}
}
//</ms_docref_global_variable>
//<ms_docref_view_tepmplate_engine>
//Create an express instance
const app = express();
//Set handlebars as your view engine
app.engine('.hbs', engine({extname: '.hbs'}));
app.set('view engine', '.hbs');
app.set("views", "./views");
//usse session configuration
app.use(session(sessionConfig));
//</ms_docref_view_tepmplate_engine>
//<ms_docref_authorization_code_url>
/**
* This method is used to generate an auth code request
* @param {string} authority: the authority to request the auth code from
* @param {array} scopes: scopes to request the auth code for
* @param {string} state: state of the application
* @param {Object} res: express middleware response object
*/
const getAuthCode = (authority, scopes, state, res) => {
// prepare the request
console.log("Fetching Authorization code")
authCodeRequest.authority = authority;
authCodeRequest.scopes = scopes;
authCodeRequest.state = state;
//Each time you fetch Authorization code, update the relevant authority in the tokenRequest configuration
tokenRequest.authority = authority;
// request an authorization code to exchange for a token
return confidentialClientApplication.getAuthCodeUrl(authCodeRequest)
.then((response) => {
console.log("\nAuthCodeURL: \n" + response);
//redirect to the auth code URL/send code to
res.redirect(response);
})
.catch((error) => {
res.status(500).send(error);
});
}
//</ms_docref_authorization_code_url>
//<ms_docref_app_endpoints>
app.get('/', (req, res) => {
res.render('signin', { showSignInButton: true });
});
app.get('/signin',(req, res)=>{
//Initiate a Auth Code Flow >> for sign in
//no scopes passed. openid, profile and offline_access will be used by default.
getAuthCode(process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY, [], APP_STATES.LOGIN, res);
});
/**
* Change password end point
*/
app.get('/password',(req, res)=>{
getAuthCode(process.env.RESET_PASSWORD_POLICY_AUTHORITY, [], APP_STATES.PASSWORD_RESET, res);
});
/**
* Edit profile end point
*/
app.get('/profile',(req, res)=>{
getAuthCode(process.env.EDIT_PROFILE_POLICY_AUTHORITY, [], APP_STATES.EDIT_PROFILE, res);
});
/**
* Sign out end point
*/
app.get('/signout',async (req, res)=>{
logoutUri = process.env.LOGOUT_ENDPOINT;
req.session.destroy(() => {
//When session destruction succeeds, notify B2C service using the logout uri.
res.redirect(logoutUri);
});
});
app.get('/redirect',(req, res)=>{
//determine the reason why the request was sent by checking the state
if (req.query.state === APP_STATES.LOGIN) {
//prepare the request for authentication
tokenRequest.code = req.query.code;
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\nAuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
console.log("\nErrorAtLogin: \n" + error);
});
}else if (req.query.state === APP_STATES.PASSWORD_RESET) {
//If the query string has a error param
if (req.query.error) {
//and if the error_description contains AADB2C90091 error code
//Means user selected the Cancel button on the password reset experience
if (JSON.stringify(req.query.error_description).includes('AADB2C90091')) {
//Send the user home with some message
//But always check if your session still exists
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name, message: 'User has cancelled the operation'});
}
}else{
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name});
}
}else if (req.query.state === APP_STATES.EDIT_PROFILE){
tokenRequest.scopes = [];
tokenRequest.code = req.query.code;
//Request token with claims, including the name that was updated.
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\AuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
//Handle error
});
}else{
res.status(500).send('We do not recognize this response!');
}
});
//</ms_docref_app_endpoints>
//start app server to listen on set port
//<ms_docref_start_node_server>
app.listen(process.env.SERVER_PORT, () => {
console.log(`Msal Node Auth Code Sample app listening on port !` + process.env.SERVER_PORT);
});
//</ms_docref_start_node_server>
authCodeRequest
对象具有属性 redirectUri
、authority
、scopes
和 state
。 该对象作为参数传递给 getAuthCodeUrl
方法。
在 index.js
文件中添加以下代码:
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
//<ms_docref_use_app_dependencies>
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const {engine} = require('express-handlebars');
const msal = require('@azure/msal-node');
//</ms_docref_use_app_dependencies>
//<ms_docref_configure_msal>
/**
* Confidential Client Application Configuration
*/
const confidentialClientConfig = {
auth: {
clientId: process.env.APP_CLIENT_ID,
authority: process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY,
clientSecret: process.env.APP_CLIENT_SECRET,
knownAuthorities: [process.env.AUTHORITY_DOMAIN], //This must be an array
redirectUri: process.env.APP_REDIRECT_URI,
validateAuthority: false
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
// Initialize MSAL Node
const confidentialClientApplication = new msal.ConfidentialClientApplication(confidentialClientConfig);
//</ms_docref_configure_msal>
//<ms_docref_global_variable>
/**
* The MSAL.js library allows you to pass your custom state as state parameter in the Request object
* By default, MSAL.js passes a randomly generated unique state parameter value in the authentication requests.
* The state parameter can also be used to encode information of the app's state before redirect.
* You can pass the user's state in the app, such as the page or view they were on, as input to this parameter.
* For more information, visit: /active-directory/develop/msal-js-pass-custom-state-authentication-request
* In this scenario, the states also serve to show the action that was requested of B2C since only one redirect URL is possible.
*/
const APP_STATES = {
LOGIN: 'login',
LOGOUT: 'logout',
PASSWORD_RESET: 'password_reset',
EDIT_PROFILE : 'update_profile'
}
/**
* Request Configuration
* We manipulate these two request objects below
* to acquire a token with the appropriate claims.
*/
const authCodeRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
const tokenRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
/**
* Using express-session middleware. Be sure to familiarize yourself with available options
* and set them as desired. Visit: https://www.npmjs.com/package/express-session
*/
const sessionConfig = {
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // set this to true on production
}
}
//</ms_docref_global_variable>
//<ms_docref_view_tepmplate_engine>
//Create an express instance
const app = express();
//Set handlebars as your view engine
app.engine('.hbs', engine({extname: '.hbs'}));
app.set('view engine', '.hbs');
app.set("views", "./views");
//usse session configuration
app.use(session(sessionConfig));
//</ms_docref_view_tepmplate_engine>
//<ms_docref_authorization_code_url>
/**
* This method is used to generate an auth code request
* @param {string} authority: the authority to request the auth code from
* @param {array} scopes: scopes to request the auth code for
* @param {string} state: state of the application
* @param {Object} res: express middleware response object
*/
const getAuthCode = (authority, scopes, state, res) => {
// prepare the request
console.log("Fetching Authorization code")
authCodeRequest.authority = authority;
authCodeRequest.scopes = scopes;
authCodeRequest.state = state;
//Each time you fetch Authorization code, update the relevant authority in the tokenRequest configuration
tokenRequest.authority = authority;
// request an authorization code to exchange for a token
return confidentialClientApplication.getAuthCodeUrl(authCodeRequest)
.then((response) => {
console.log("\nAuthCodeURL: \n" + response);
//redirect to the auth code URL/send code to
res.redirect(response);
})
.catch((error) => {
res.status(500).send(error);
});
}
//</ms_docref_authorization_code_url>
//<ms_docref_app_endpoints>
app.get('/', (req, res) => {
res.render('signin', { showSignInButton: true });
});
app.get('/signin',(req, res)=>{
//Initiate a Auth Code Flow >> for sign in
//no scopes passed. openid, profile and offline_access will be used by default.
getAuthCode(process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY, [], APP_STATES.LOGIN, res);
});
/**
* Change password end point
*/
app.get('/password',(req, res)=>{
getAuthCode(process.env.RESET_PASSWORD_POLICY_AUTHORITY, [], APP_STATES.PASSWORD_RESET, res);
});
/**
* Edit profile end point
*/
app.get('/profile',(req, res)=>{
getAuthCode(process.env.EDIT_PROFILE_POLICY_AUTHORITY, [], APP_STATES.EDIT_PROFILE, res);
});
/**
* Sign out end point
*/
app.get('/signout',async (req, res)=>{
logoutUri = process.env.LOGOUT_ENDPOINT;
req.session.destroy(() => {
//When session destruction succeeds, notify B2C service using the logout uri.
res.redirect(logoutUri);
});
});
app.get('/redirect',(req, res)=>{
//determine the reason why the request was sent by checking the state
if (req.query.state === APP_STATES.LOGIN) {
//prepare the request for authentication
tokenRequest.code = req.query.code;
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\nAuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
console.log("\nErrorAtLogin: \n" + error);
});
}else if (req.query.state === APP_STATES.PASSWORD_RESET) {
//If the query string has a error param
if (req.query.error) {
//and if the error_description contains AADB2C90091 error code
//Means user selected the Cancel button on the password reset experience
if (JSON.stringify(req.query.error_description).includes('AADB2C90091')) {
//Send the user home with some message
//But always check if your session still exists
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name, message: 'User has cancelled the operation'});
}
}else{
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name});
}
}else if (req.query.state === APP_STATES.EDIT_PROFILE){
tokenRequest.scopes = [];
tokenRequest.code = req.query.code;
//Request token with claims, including the name that was updated.
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\AuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
//Handle error
});
}else{
res.status(500).send('We do not recognize this response!');
}
});
//</ms_docref_app_endpoints>
//start app server to listen on set port
//<ms_docref_start_node_server>
app.listen(process.env.SERVER_PORT, () => {
console.log(`Msal Node Auth Code Sample app listening on port !` + process.env.SERVER_PORT);
});
//</ms_docref_start_node_server>
快速路由包括:
/
:- 用于输入 Web 应用。
- 它呈现
signin
页面。
/signin
:- 登录时使用。
- 它调用
getAuthCode()
方法并向其传递“登录和注册”用户流/策略的authority
、APP_STATES.LOGIN
和一个空的scopes
数组。 - 必要时,它会使系统质询你,要求你输入凭据。 如果你没有帐户,它会提示你注册。
- 此路由生成的最终响应包括由 Azure AD B2C 发回到
/redirect
路由的授权代码。
/password
:- 重置密码时使用。
- 它调用
getAuthCode()
方法并向其传递“密码重置”用户流/策略的authority
、APP_STATES.PASSWORD_RESET
和一个空的scopes
数组。 - 使用它,你可以通过密码重置体验来更改密码,也可以取消该操作。
- 此路由生成的最终响应包括由 Azure AD B2C 发回到
/redirect
路由的授权代码。 如果取消该操作,则会发回一个错误。
/profile
:- 更新个人资料时使用。
- 它调用
getAuthCode()
方法并向其传递“个人资料编辑”用户流/策略的authority
、APP_STATES.EDIT_PROFILE
和一个空的scopes
数组。 - 使用它,你可以更新个人资料。你使用个人资料编辑体验。
- 此路由生成的最终响应包括由 Azure AD B2C 发回到
/redirect
路由的授权代码。
/signout
:- 注销时使用。
- Web 应用会清除此会话,然后对 Azure AD B2C 注销终结点进行 HTTP 调用。
/redirect
:- 在 Azure 门户中设置为 Web 应用重定向 URI 的路由。
- 它使用来自 Azure AD B2C 的请求中的
state
查询参数来区分从 Web 应用发出的请求。 它处理来自 Azure AD B2C 的所有重定向,但注销除外。 - 如果应用状态为
APP_STATES.LOGIN
,则使用获取的授权代码通过acquireTokenByCode()
方法检索令牌。 此令牌包括idToken
和idTokenClaims
,用于识别用户。 - 如果应用状态为
APP_STATES.PASSWORD_RESET
,则它会处理任何错误,例如user cancelled the operation
。AADB2C90091
错误代码会标识此错误。 否则,它将确定下一个用户体验。 - 如果应用状态为
APP_STATES.EDIT_PROFILE
,它将使用授权代码来获取令牌。 令牌包含idTokenClaims
,后者包含新更改。
步骤 6:启动 Node 服务器
若要启动 Node 服务器,请在 index.js
文件中添加以下代码:
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
//<ms_docref_use_app_dependencies>
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const {engine} = require('express-handlebars');
const msal = require('@azure/msal-node');
//</ms_docref_use_app_dependencies>
//<ms_docref_configure_msal>
/**
* Confidential Client Application Configuration
*/
const confidentialClientConfig = {
auth: {
clientId: process.env.APP_CLIENT_ID,
authority: process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY,
clientSecret: process.env.APP_CLIENT_SECRET,
knownAuthorities: [process.env.AUTHORITY_DOMAIN], //This must be an array
redirectUri: process.env.APP_REDIRECT_URI,
validateAuthority: false
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
// Initialize MSAL Node
const confidentialClientApplication = new msal.ConfidentialClientApplication(confidentialClientConfig);
//</ms_docref_configure_msal>
//<ms_docref_global_variable>
/**
* The MSAL.js library allows you to pass your custom state as state parameter in the Request object
* By default, MSAL.js passes a randomly generated unique state parameter value in the authentication requests.
* The state parameter can also be used to encode information of the app's state before redirect.
* You can pass the user's state in the app, such as the page or view they were on, as input to this parameter.
* For more information, visit: /active-directory/develop/msal-js-pass-custom-state-authentication-request
* In this scenario, the states also serve to show the action that was requested of B2C since only one redirect URL is possible.
*/
const APP_STATES = {
LOGIN: 'login',
LOGOUT: 'logout',
PASSWORD_RESET: 'password_reset',
EDIT_PROFILE : 'update_profile'
}
/**
* Request Configuration
* We manipulate these two request objects below
* to acquire a token with the appropriate claims.
*/
const authCodeRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
const tokenRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
/**
* Using express-session middleware. Be sure to familiarize yourself with available options
* and set them as desired. Visit: https://www.npmjs.com/package/express-session
*/
const sessionConfig = {
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // set this to true on production
}
}
//</ms_docref_global_variable>
//<ms_docref_view_tepmplate_engine>
//Create an express instance
const app = express();
//Set handlebars as your view engine
app.engine('.hbs', engine({extname: '.hbs'}));
app.set('view engine', '.hbs');
app.set("views", "./views");
//usse session configuration
app.use(session(sessionConfig));
//</ms_docref_view_tepmplate_engine>
//<ms_docref_authorization_code_url>
/**
* This method is used to generate an auth code request
* @param {string} authority: the authority to request the auth code from
* @param {array} scopes: scopes to request the auth code for
* @param {string} state: state of the application
* @param {Object} res: express middleware response object
*/
const getAuthCode = (authority, scopes, state, res) => {
// prepare the request
console.log("Fetching Authorization code")
authCodeRequest.authority = authority;
authCodeRequest.scopes = scopes;
authCodeRequest.state = state;
//Each time you fetch Authorization code, update the relevant authority in the tokenRequest configuration
tokenRequest.authority = authority;
// request an authorization code to exchange for a token
return confidentialClientApplication.getAuthCodeUrl(authCodeRequest)
.then((response) => {
console.log("\nAuthCodeURL: \n" + response);
//redirect to the auth code URL/send code to
res.redirect(response);
})
.catch((error) => {
res.status(500).send(error);
});
}
//</ms_docref_authorization_code_url>
//<ms_docref_app_endpoints>
app.get('/', (req, res) => {
res.render('signin', { showSignInButton: true });
});
app.get('/signin',(req, res)=>{
//Initiate a Auth Code Flow >> for sign in
//no scopes passed. openid, profile and offline_access will be used by default.
getAuthCode(process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY, [], APP_STATES.LOGIN, res);
});
/**
* Change password end point
*/
app.get('/password',(req, res)=>{
getAuthCode(process.env.RESET_PASSWORD_POLICY_AUTHORITY, [], APP_STATES.PASSWORD_RESET, res);
});
/**
* Edit profile end point
*/
app.get('/profile',(req, res)=>{
getAuthCode(process.env.EDIT_PROFILE_POLICY_AUTHORITY, [], APP_STATES.EDIT_PROFILE, res);
});
/**
* Sign out end point
*/
app.get('/signout',async (req, res)=>{
logoutUri = process.env.LOGOUT_ENDPOINT;
req.session.destroy(() => {
//When session destruction succeeds, notify B2C service using the logout uri.
res.redirect(logoutUri);
});
});
app.get('/redirect',(req, res)=>{
//determine the reason why the request was sent by checking the state
if (req.query.state === APP_STATES.LOGIN) {
//prepare the request for authentication
tokenRequest.code = req.query.code;
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\nAuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
console.log("\nErrorAtLogin: \n" + error);
});
}else if (req.query.state === APP_STATES.PASSWORD_RESET) {
//If the query string has a error param
if (req.query.error) {
//and if the error_description contains AADB2C90091 error code
//Means user selected the Cancel button on the password reset experience
if (JSON.stringify(req.query.error_description).includes('AADB2C90091')) {
//Send the user home with some message
//But always check if your session still exists
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name, message: 'User has cancelled the operation'});
}
}else{
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name});
}
}else if (req.query.state === APP_STATES.EDIT_PROFILE){
tokenRequest.scopes = [];
tokenRequest.code = req.query.code;
//Request token with claims, including the name that was updated.
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\AuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
//Handle error
});
}else{
res.status(500).send('We do not recognize this response!');
}
});
//</ms_docref_app_endpoints>
//start app server to listen on set port
//<ms_docref_start_node_server>
app.listen(process.env.SERVER_PORT, () => {
console.log(`Msal Node Auth Code Sample app listening on port !` + process.env.SERVER_PORT);
});
//</ms_docref_start_node_server>
在 index.js
文件中进行所有必需的更改后,它看起来应类似于以下文件:
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
//<ms_docref_use_app_dependencies>
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const {engine} = require('express-handlebars');
const msal = require('@azure/msal-node');
//</ms_docref_use_app_dependencies>
//<ms_docref_configure_msal>
/**
* Confidential Client Application Configuration
*/
const confidentialClientConfig = {
auth: {
clientId: process.env.APP_CLIENT_ID,
authority: process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY,
clientSecret: process.env.APP_CLIENT_SECRET,
knownAuthorities: [process.env.AUTHORITY_DOMAIN], //This must be an array
redirectUri: process.env.APP_REDIRECT_URI,
validateAuthority: false
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
// Initialize MSAL Node
const confidentialClientApplication = new msal.ConfidentialClientApplication(confidentialClientConfig);
//</ms_docref_configure_msal>
//<ms_docref_global_variable>
/**
* The MSAL.js library allows you to pass your custom state as state parameter in the Request object
* By default, MSAL.js passes a randomly generated unique state parameter value in the authentication requests.
* The state parameter can also be used to encode information of the app's state before redirect.
* You can pass the user's state in the app, such as the page or view they were on, as input to this parameter.
* For more information, visit: /active-directory/develop/msal-js-pass-custom-state-authentication-request
* In this scenario, the states also serve to show the action that was requested of B2C since only one redirect URL is possible.
*/
const APP_STATES = {
LOGIN: 'login',
LOGOUT: 'logout',
PASSWORD_RESET: 'password_reset',
EDIT_PROFILE : 'update_profile'
}
/**
* Request Configuration
* We manipulate these two request objects below
* to acquire a token with the appropriate claims.
*/
const authCodeRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
const tokenRequest = {
redirectUri: confidentialClientConfig.auth.redirectUri,
};
/**
* Using express-session middleware. Be sure to familiarize yourself with available options
* and set them as desired. Visit: https://www.npmjs.com/package/express-session
*/
const sessionConfig = {
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // set this to true on production
}
}
//</ms_docref_global_variable>
//<ms_docref_view_tepmplate_engine>
//Create an express instance
const app = express();
//Set handlebars as your view engine
app.engine('.hbs', engine({extname: '.hbs'}));
app.set('view engine', '.hbs');
app.set("views", "./views");
//usse session configuration
app.use(session(sessionConfig));
//</ms_docref_view_tepmplate_engine>
//<ms_docref_authorization_code_url>
/**
* This method is used to generate an auth code request
* @param {string} authority: the authority to request the auth code from
* @param {array} scopes: scopes to request the auth code for
* @param {string} state: state of the application
* @param {Object} res: express middleware response object
*/
const getAuthCode = (authority, scopes, state, res) => {
// prepare the request
console.log("Fetching Authorization code")
authCodeRequest.authority = authority;
authCodeRequest.scopes = scopes;
authCodeRequest.state = state;
//Each time you fetch Authorization code, update the relevant authority in the tokenRequest configuration
tokenRequest.authority = authority;
// request an authorization code to exchange for a token
return confidentialClientApplication.getAuthCodeUrl(authCodeRequest)
.then((response) => {
console.log("\nAuthCodeURL: \n" + response);
//redirect to the auth code URL/send code to
res.redirect(response);
})
.catch((error) => {
res.status(500).send(error);
});
}
//</ms_docref_authorization_code_url>
//<ms_docref_app_endpoints>
app.get('/', (req, res) => {
res.render('signin', { showSignInButton: true });
});
app.get('/signin',(req, res)=>{
//Initiate a Auth Code Flow >> for sign in
//no scopes passed. openid, profile and offline_access will be used by default.
getAuthCode(process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY, [], APP_STATES.LOGIN, res);
});
/**
* Change password end point
*/
app.get('/password',(req, res)=>{
getAuthCode(process.env.RESET_PASSWORD_POLICY_AUTHORITY, [], APP_STATES.PASSWORD_RESET, res);
});
/**
* Edit profile end point
*/
app.get('/profile',(req, res)=>{
getAuthCode(process.env.EDIT_PROFILE_POLICY_AUTHORITY, [], APP_STATES.EDIT_PROFILE, res);
});
/**
* Sign out end point
*/
app.get('/signout',async (req, res)=>{
logoutUri = process.env.LOGOUT_ENDPOINT;
req.session.destroy(() => {
//When session destruction succeeds, notify B2C service using the logout uri.
res.redirect(logoutUri);
});
});
app.get('/redirect',(req, res)=>{
//determine the reason why the request was sent by checking the state
if (req.query.state === APP_STATES.LOGIN) {
//prepare the request for authentication
tokenRequest.code = req.query.code;
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\nAuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
console.log("\nErrorAtLogin: \n" + error);
});
}else if (req.query.state === APP_STATES.PASSWORD_RESET) {
//If the query string has a error param
if (req.query.error) {
//and if the error_description contains AADB2C90091 error code
//Means user selected the Cancel button on the password reset experience
if (JSON.stringify(req.query.error_description).includes('AADB2C90091')) {
//Send the user home with some message
//But always check if your session still exists
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name, message: 'User has cancelled the operation'});
}
}else{
res.render('signin', {showSignInButton: false, givenName: req.session.sessionParams.user.idTokenClaims.given_name});
}
}else if (req.query.state === APP_STATES.EDIT_PROFILE){
tokenRequest.scopes = [];
tokenRequest.code = req.query.code;
//Request token with claims, including the name that was updated.
confidentialClientApplication.acquireTokenByCode(tokenRequest).then((response)=>{
req.session.sessionParams = {user: response.account, idToken: response.idToken};
console.log("\AuthToken: \n" + JSON.stringify(response));
res.render('signin',{showSignInButton: false, givenName: response.account.idTokenClaims.given_name});
}).catch((error)=>{
//Handle error
});
}else{
res.status(500).send('We do not recognize this response!');
}
});
//</ms_docref_app_endpoints>
//start app server to listen on set port
//<ms_docref_start_node_server>
app.listen(process.env.SERVER_PORT, () => {
console.log(`Msal Node Auth Code Sample app listening on port !` + process.env.SERVER_PORT);
});
//</ms_docref_start_node_server>
步骤 7:运行 Web 应用
按照运行 Web 应用中的步骤测试 Node.js Web 应用。