Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Importante
A partir de 1º de maio de 2025, o Azure AD B2C não estará mais disponível para compra para novos clientes. Saiba mais nas nossas Perguntas Frequentes.
Neste artigo, você aprenderá a adicionar a autenticação do Azure Ative Directory B2C (Azure AD B2C) em seu próprio aplicativo Web Node.js. Você permitirá que os usuários entrem, saiam, atualizem o perfil e redefina a senha usando os fluxos de usuário do Azure AD B2C. Este artigo usa a Microsoft Authentication Library (MSAL) for Node para simplificar a adição de autenticação à sua aplicação web Node.
O objetivo deste artigo é substituir o aplicativo de exemplo usado em Configurar autenticação em um aplicativo Web de exemplo Node.js usando o Azure AD B2C por seu próprio aplicativo Web Node.js.
Este artigo usa o Node.js e o Express para criar um aplicativo Web Node.js básico. As visualizações da aplicação usam Handlebars.
Pré-requisitos
- Conclua as etapas em Configurar autenticação em um aplicativo Web de Node.js de exemplo usando o Azure AD B2C. Você criará fluxos de usuário do Azure AD B2C e registrará um aplicativo Web no portal do Azure.
Etapa 1: Criar o projeto Node.js
Crie uma pasta para alojar a sua aplicação de node, como active-directory-b2c-msal-node-sign-in-sign-out-webapp
.
No terminal, altere o diretório para a pasta do aplicativo Node, como
cd active-directory-b2c-msal-node-sign-in-sign-out-webapp
, e executenpm init -y
. Este comando cria um arquivo depackage.json
padrão para seu projeto Node.js.No terminal, corra
npm install express
. Este comando instala a estrutura Express.Crie mais pastas e arquivos para obter a seguinte estrutura de projeto:
active-directory-b2c-msal-node-sign-in-sign-out-webapp/ ├── index.js └── package.json └── .env └── views/ └── layouts/ └── main.hbs └── signin.hbs
A pasta views
contém arquivos de Handlebars para a interface de utilizador da aplicação.
Etapa 2: Instalar dependências do aplicativo
No seu terminal, instale os pacotes dotenv
, express-handlebars
, express-session
e @azure/msal-node
executando os seguintes comandos:
npm install dotenv
npm install express-handlebars
npm install express-session
npm install @azure/msal-node
Etapa 3: Criar componentes da interface do usuário do aplicativo
main.hbs
No arquivo, adicione o seguinte código:
<!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>
O arquivo main.hbs
está na pasta layout
. Ele deve conter qualquer código HTML necessário em todo o seu aplicativo. Qualquer interface gráfica que mude de um modo de exibição para outro, como em signin.hbs
, é posicionada no espaço designado {{{body}}}
.
O main.hbs
arquivo implementa a interface do usuário construída com a estrutura CSS do Bootstrap 5. Você verá os componentes Editar senha, Redefinir senha e Sair da interface do usuário (botões) quando estiver conectado. Você verá Entrar quando estiver desconectado. Este comportamento é controlado pela variável booleana showSignInButton
, que o servidor da aplicação envia.
signin.hbs
No arquivo, adicione o seguinte código:
<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>
Etapa 4: Configurar o servidor Web e o cliente MSAL
.env
No arquivo, adicione o código a seguir e atualize-o conforme explicado em Configurar o aplicativo Web de exemplo.#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
No arquivo
index.js
, adicione o seguinte código para usar as dependências do aplicativo:require('dotenv').config(); const express = require('express'); const session = require('express-session'); const {engine} = require('express-handlebars'); const msal = require('@azure/msal-node');
No arquivo
index.js
, adicione o seguinte código para configurar a biblioteca de autenticação:/** * 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
é o objeto de configuração MSAL usado para se conectar aos endpoints de autenticação do tenant do Azure AD B2C.Para adicionar mais variáveis globais no
index.js
arquivo, adicione o seguinte código:/** * 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
: Usado para diferenciar as respostas recebidas do Azure AD B2C marcando solicitações. Há apenas um URI de redirecionamento para qualquer número de solicitações enviadas ao Azure AD B2C. -
authCodeRequest
: O objeto de configuração usado para recuperar o código de autorização. -
tokenRequest
: O objeto de configuração usado para adquirir um token por código de autorização. -
sessionConfig
: O objeto de configuração para a sessão Express.
-
Para definir o mecanismo de modelo de exibição e a configuração de sessão Express, adicione o seguinte código no
index.js
arquivo://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));
Etapa 5: adicionar rotas expressas
Antes de adicionar a rota do aplicativo, adicione a lógica que recupera a URL do código de autorização, que é a primeira etapa do fluxo de concessão do código de autorização.
index.js
No arquivo, adicione o seguinte código:
/**
* 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);
});
}
O authCodeRequest
objeto tem as propriedades redirectUri
, authority
, scopes
, e state
. O objeto é passado para o getAuthCodeUrl
método como um parâmetro.
index.js
No arquivo, adicione o seguinte código:
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!');
}
});
As rotas expressas são:
-
/
:- Ele é usado para entrar no aplicativo Web.
- Ele renderiza a
signin
página.
-
/signin
:- É utilizado quando você inicia sessão.
- Ele chama o método
getAuthCode()
e passaauthority
para os fluxos/políticas de utilizador Entrar e inscrever-se,APP_STATES.LOGIN
, e uma matriz vaziascopes
a ele. - Se necessário, pode desafiar você a inserir suas credenciais. Se você não tiver uma conta, ele solicitará que você se inscreva.
- A resposta final que resulta desta rota inclui um código de autorização do Azure AD B2C, que é reenviado para a rota
/redirect
.
-
/password
:- Ele é usado quando você redefine uma senha.
- Ele chama o
getAuthCode()
método e passaauthority
para o fluxo/políticaAPP_STATES.PASSWORD_RESET
e uma matriz vaziascopes
para ele. - Ele permite que você altere sua senha usando a experiência de redefinição de senha, ou eles podem cancelar a operação.
- A resposta final que resulta desta rota inclui um código de autorização do Azure AD B2C, que é reenviado para a rota
/redirect
. Se você cancelar a operação, um erro será postado de volta.
-
/profile
:- Ele é usado quando você atualiza seu perfil.
- Ele chama o
getAuthCode()
método e passaauthority
para o fluxo/política do usuárioAPP_STATES.EDIT_PROFILE
e uma matriz vaziascopes
para ele. - Ele permite que você atualize seu perfil e use a experiência de edição de perfil.
- A resposta final que resulta desta rota inclui um código de autorização do Azure AD B2C, que é reenviado para a rota
/redirect
.
-
/signout
:- É utilizado quando termina a sessão.
- A aplicação web limpa a sessão e faz uma chamada HTTP para o endpoint de sign out do Azure AD B2C.
-
/redirect
:- É a rota definida como URI de redirecionamento para o aplicativo Web no portal do Azure.
- Ele utiliza o parâmetro de consulta
state
na solicitação do Azure AD B2C para diferenciar entre solicitações feitas a partir da aplicação web. Ele lida com todos os redirecionamentos do Azure AD B2C, exceto no caso de término de sessão. - Se o estado do aplicativo for
APP_STATES.LOGIN
, o código de autorização adquirido será usado para recuperar um token por meio doacquireTokenByCode()
método. Este token incluiidToken
eidTokenClaims
, que são usados para identificação do usuário. - Se o estado da aplicação for
APP_STATES.PASSWORD_RESET
, ele lidará com qualquer erro, comouser cancelled the operation
. OAADB2C90091
código de erro identifica esse erro. Caso contrário, ele decide a próxima experiência do usuário. - Se o estado do aplicativo for
APP_STATES.EDIT_PROFILE
, ele usará o código de autorização para adquirir um token. O token contémidTokenClaims
, que inclui as novas alterações.
Etapa 6: Iniciar o servidor Node
Para iniciar o servidor Node, adicione o seguinte código no index.js
arquivo:
app.listen(process.env.SERVER_PORT, () => {
console.log(`Msal Node Auth Code Sample app listening on port !` + process.env.SERVER_PORT);
});
Depois de fazer todas as alterações necessárias no index.js
arquivo, ele deve ser semelhante ao seguinte arquivo:
/*
* 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>
Etapa 7: executar seu aplicativo Web
Siga as etapas em Executar seu aplicativo Web para testar seu aplicativo Web Node.js.