Habilitación de la autenticación en su propia aplicación web de Node mediante Azure Active Directory B2C
En este artículo, aprenderá a agregar la autenticación de Azure Active Directory B2C (Azure AD B2C) en su propia aplicación web de Node.js. Permitirá a los usuarios iniciar sesión, cerrar sesión, actualizar el perfil y restablecer la contraseña mediante flujos de usuario de Azure AD B2C. En este artículo se usa la Biblioteca de autenticación de Microsoft (MSAL) para Node para simplificar la adición de autenticación a la aplicación web de Node.
El objetivo de este artículo es sustituir la aplicación de ejemplo que usó en Configuración de la autenticación en una aplicación web Node.js de ejemplo mediante Azure AD B2C con su propia aplicación web Node.js web.
En este artículo se usa Node.js y Express para crear una aplicación web de Node.js básica. Las vistas de la aplicación usan Handlebars.
Requisitos previos
- Complete los pasos descritos en Configuración de la autenticación en una aplicación web Node.js de ejemplo mediante Azure AD B2C. Creará flujos de usuario de Azure AD B2C y registrará una aplicación web en Azure Portal.
Paso 1: Creación del proyecto de Node
Cree una carpeta para hospedar la aplicación de Node, como active-directory-b2c-msal-node-sign-in-sign-out-webapp
.
En el terminal, cambie el directorio a la carpeta de la aplicación de Node, como
cd active-directory-b2c-msal-node-sign-in-sign-out-webapp
, y ejecutenpm init -y
. Este comando crea un archivopackage.json
predeterminado para el proyecto de Node.js.En el terminal, ejecute
npm install express
. Este comando instala el marco Express.Cree más carpetas y archivos para lograr esta estructura de proyecto:
active-directory-b2c-msal-node-sign-in-sign-out-webapp/ ├── index.js └── package.json └── .env └── views/ └── layouts/ └── main.hbs └── signin.hbs
La carpeta views
contiene los archivos de Handlebars para la interfaz de usuario de la aplicación.
Paso 2: Instalación de dependencias de aplicaciones
En el terminal, ejecute los comandos siguientes para instalar los paquetes dotenv
, express-handlebars
, express-session
y @azure/msal-node
:
npm install dotenv
npm install express-handlebars
npm install express-session
npm install @azure/msal-node
Paso 3: Compilación de componentes de la interfaz de usuario de la aplicación
En el archivo main.hbs
, agregue el código siguiente:
<!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>
El archivo main.hbs
está en la carpeta layout
. Debe contener cualquier código HTML que sea necesario en toda la aplicación. Cualquier interfaz de usuario que cambie de una vista a otra, como en signin.hbs
, se coloca en el marcador de posición que se muestra como {{{body}}}
.
El archivo main.hbs
implementa la interfaz de usuario creada con el marco CSS Bootstrap 5. Verá los componentes de la interfaz de usuario Editar contraseña, Restablecer contraseña y Cerrar sesión (botones) cuando haya iniciado sesión. Verá Iniciar sesión cuando cierre la sesión. La variable booleana showSignInButton
, enviada por el servidor de aplicaciones, controla este comportamiento.
En el archivo signin.hbs
, agregue el código siguiente:
<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>
Paso 4: Configuración del servidor web y el cliente MSAL
En el archivo
.env
, agregue el código siguiente y actualícelo como se explica en Configuración de la aplicación web de ejemplo.#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.com/<your-tenant-name>.onmicrosoft.com/<sign-in-sign-up-user-flow-name> #B2C password reset user flow/policy authority RESET_PASSWORD_POLICY_AUTHORITY=https://<your-tenant-name>.b2clogin.com/<your-tenant-name>.onmicrosoft.com/<reset-password-user-flow-name> #B2C edit profile user flow/policy authority EDIT_PROFILE_POLICY_AUTHORITY=https://<your-tenant-name>.b2clogin.com/<your-tenant-name>.onmicrosoft.com/<profile-edit-user-flow-name> #B2C authority domain AUTHORITY_DOMAIN=https://<your-tenant-name>.b2clogin.com #client redirect url APP_REDIRECT_URI=http://localhost:3000/redirect #Logout endpoint LOGOUT_ENDPOINT=https://<your-tenant-name>.b2clogin.com/<your-tenant-name>.onmicrosoft.com/<sign-in-sign-up-user-flow-name>/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000
En el archivo
index.js
, agregue el código siguiente para usar las dependencias de la aplicación:require('dotenv').config(); const express = require('express'); const session = require('express-session'); const {engine} = require('express-handlebars'); const msal = require('@azure/msal-node');
En el archivo
index.js
, agregue el código siguiente para configurar la biblioteca de autenticación:/** * 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);
confidentialClientConfig
es el objeto de configuración de MSAL que se usa para conectarse a los puntos de conexión de autenticación del inquilino de Azure AD B2C.Para agregar más variables globales en el archivo
index.js
, agregue el código siguiente:/** * 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: https://docs.microsoft.com/azure/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 } }
APP_STATES
: se usa para diferenciar entre las respuestas recibidas de Azure AD B2C mediante el etiquetado de las solicitudes. Solo hay un identificador URI de redirección para cualquier número de solicitudes enviadas a Azure AD B2C.authCodeRequest
: objeto de configuración que se usa para recuperar el código de autorización.tokenRequest
: objeto de configuración que se usa para adquirir un token mediante el código de autorización.sessionConfig
: objeto de configuración de la sesión de Express.
Para establecer el motor de plantillas de las vistas y la configuración de la sesión de Express, agregue el código siguiente en el archivo
index.js
://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));
Paso 5: Adición de rutas rápidas
Antes de agregar la ruta de la aplicación, agregue la lógica que recupera la dirección URL del código de autorización, que es el primer fragmento del flujo de concesión de código de autorización. En el archivo index.js
, agregue el código siguiente:
/**
* 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);
});
}
El objeto authCodeRequest
tiene las propiedades redirectUri
, authority
, scopes
y state
. El objeto se pasa como un parámetro al método getAuthCodeUrl
.
En el archivo index.js
, agregue el código siguiente:
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!');
}
});
Las rutas rápidas son:
/
:- Se usa para entrar a la aplicación web.
- Representa la página
signin
.
/signin
:- Se usa cuando inicia sesión.
- Llama al método
getAuthCode()
y pasa el elementoauthority
para el flujo de usuario y la directiva de inicio de sesión y registro,APP_STATES.LOGIN
, y una matrizscopes
vacía. - Si es necesario, se produce un desafío para que escriba sus credenciales. Si no tiene una cuenta, se le pedirá que se registre.
- La respuesta final que resulta de esta ruta incluye un código de autorización de Azure AD B2C que se devuelve a la ruta
/redirect
.
/password
:- Se usa al restablecer una contraseña.
- Llama al método
getAuthCode()
y pasa el elementoauthority
para el flujo de usuario y la directiva de restablecimiento de contraseña,APP_STATES.PASSWORD_RESET
, y una matrizscopes
vacía. - Le permite cambiar la contraseña mediante la experiencia de restablecimiento de contraseña, o bien pueden cancelar la operación.
- La respuesta final que resulta de esta ruta incluye un código de autorización de Azure AD B2C que se devuelve a la ruta
/redirect
. Si cancela la operación, se devuelve un error.
/profile
:- Se usa al actualizar el perfil.
- Llama al método
getAuthCode()
y pasa el elementoauthority
para el flujo de usuario y la directiva de edición de perfil,APP_STATES.EDIT_PROFILE
, y una matrizscopes
vacía. - Le permite actualizar el perfil y usar la experiencia de edición de perfiles.
- La respuesta final que resulta de esta ruta incluye un código de autorización de Azure AD B2C que se devuelve a la ruta
/redirect
.
/signout
:- Se usa al cerrar sesión.
- La aplicación web borra la sesión y realiza una llamada HTTP al punto de conexión de cierre de sesión de Azure AD B2C.
/redirect
:- Se trata de la ruta establecida como URI de redireccionamiento para la aplicación web en Azure Portal.
- Usa el parámetro de consulta
state
de la solicitud de Azure AD B2C a fin de diferenciar entre las solicitudes que se realizan desde la aplicación web. Controla todas las redirecciones desde Azure AD B2C, excepto para el cierre de sesión. - Si el estado de la aplicación es
APP_STATES.LOGIN
, el código de autorización adquirido se usa para recuperar un token mediante el métodoacquireTokenByCode()
. Este token incluye los elementosidToken
yidTokenClaims
, que se usan para la identificación del usuario. - Si el estado de la aplicación es
APP_STATES.PASSWORD_RESET
, controla cualquier error, comouser cancelled the operation
. El código de errorAADB2C90091
identifica este error. De lo contrario, decide la siguiente experiencia del usuario. - Si el estado de la aplicación es
APP_STATES.EDIT_PROFILE
, usa el código de autorización para adquirir un token. El token contiene el elementoidTokenClaims
, que incluye los nuevos cambios.
Paso 6: Inicio del servidor de Node
Para iniciar el servidor de Node, agregue el código siguiente en el archivo index.js
:
app.listen(process.env.SERVER_PORT, () => {
console.log(`Msal Node Auth Code Sample app listening on port !` + process.env.SERVER_PORT);
});
Después de realizar todos los cambios necesarios en el archivo index.js
, debería ser similar al siguiente archivo:
/*
* 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: https://docs.microsoft.com/azure/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>
Paso 7: Ejecución de la aplicación web
Siga los pasos descritos en Ejecución de la aplicación web para probar la aplicación web Node.js.
Pasos siguientes
- Obtenga información sobre cómo personalizar y mejorar la experiencia de autenticación de Azure AD B2C para la aplicación web.