Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
In questa esercitazione si crea un'app Web che accede agli utenti e acquisisce i token di accesso per chiamare Microsoft Graph. L'app Web compilata usa Microsoft Authentication Library (MSAL) per Node.
Seguire la procedura descritta in questa esercitazione per:
- Registrare l'applicazione nel portale di Azure
- Creare un progetto di app Web Express
- Installare i pacchetti della libreria di autenticazione
- Aggiungere i dettagli di registrazione dell'app
- Aggiungere il codice per l'accesso utente
- Testare l'app Per altre informazioni, vedere il codice di esempio che illustra come usare MSAL Node per accedere, disconnettersi e acquisire un token di accesso per una risorsa protetta, ad esempio Microsoft Graph.
Prerequisiti
- Node.js
- visual Studio Code o un altro editor di codice
Registrare l'applicazione
Prima di tutto, completare i passaggi descritti in Registrare un'applicazione con Microsoft Identity Platform per registrare l'app.
Usare le impostazioni seguenti per la registrazione dell'app:
- Nome:
ExpressWebApp
(suggerito) - Tipi di account supportati: solo gli account in questa directory organizzativa
- Tipo di piattaforma: Web
- URI di reindirizzamento:
http://localhost:3000/auth/redirect
- Segreto client:
*********
(registrare questo valore da usare in un passaggio successivo, che viene visualizzato una sola volta)
Creare il progetto
Usare lo strumento generatore di applicazioni express per creare uno scheletro di applicazioni.
npm install -g express-generator
- Creare quindi una struttura dell'applicazione come indicato di seguito:
express --view=hbs /ExpressWebApp && cd /ExpressWebApp
npm install
È ora disponibile una semplice app Web Express. La struttura di file e cartelle del progetto dovrebbe essere simile alla struttura di cartelle seguente:
ExpressWebApp/
├── bin/
| └── wwww
├── public/
| ├── images/
| ├── javascript/
| └── stylesheets/
| └── style.css
├── routes/
| ├── index.js
| └── users.js
├── views/
| ├── error.hbs
| ├── index.hbs
| └── layout.hbs
├── app.js
└── package.json
Installare la libreria di autenticazione
Trova la directory principale del tuo progetto in un terminale e installa il pacchetto MSAL Node tramite npm.
npm install --save @azure/msal-node
Installare altre dipendenze
L'esempio di app Web in questa esercitazione usa il pacchetto di sessione rapida per la gestione delle sessioni, pacchetto dotenv per la lettura dei parametri di ambiente durante lo sviluppo e axios per effettuare chiamate di rete all'API Microsoft Graph. Installare questi elementi tramite npm:
npm install --save express-session dotenv axios
Aggiungere i dettagli di registrazione dell'app
- Creare un file .env.dev nella radice della cartella del progetto. Aggiungere quindi il codice seguente:
CLOUD_INSTANCE="Enter_the_Cloud_Instance_Id_Here" # cloud instance string should end with a trailing slash
TENANT_ID="Enter_the_Tenant_Info_Here"
CLIENT_ID="Enter_the_Application_Id_Here"
CLIENT_SECRET="Enter_the_Client_Secret_Here"
REDIRECT_URI="http://localhost:3000/auth/redirect"
POST_LOGOUT_REDIRECT_URI="http://localhost:3000"
GRAPH_API_ENDPOINT="Enter_the_Graph_Endpoint_Here" # graph api endpoint string should end with a trailing slash
EXPRESS_SESSION_SECRET="Enter_the_Express_Session_Secret_Here"
Compilare questi dettagli con i valori ottenuti dal portale di registrazione delle app di Azure:
-
Enter_the_Cloud_Instance_Id_Here
: istanza cloud di Azure in cui è registrata l'applicazione.- Per il cloud di Azure principale (o globale), immettere
https://login.microsoftonline.com/
(includere la barra obliqua finale). - Per cloud nazionali (ad esempio, Cina), è possibile trovare i valori appropriati in cloud nazionali.
- Per il cloud di Azure principale (o globale), immettere
-
Enter_the_Tenant_Info_here
deve essere uno dei parametri seguenti:- Se la tua applicazione supporta gli account in questa directory organizzativa, sostituire questo valore con l'ID Tenant o il nome del Tenant . Ad esempio,
contoso.microsoft.com
. - Se l'applicazione supporta gli account in qualsiasi directory organizzativa, sostituisci questo valore con
organizations
. - Se l'applicazione supporta account in una qualsiasi directory organizzativa e account Microsoft personali, sostituisci questo valore con
common
. - Per limitare il supporto solo agli account Microsoft personali , sostituire questo valore con
consumers
.
- Se la tua applicazione supporta gli account in questa directory organizzativa, sostituire questo valore con l'ID Tenant o il nome del Tenant . Ad esempio,
-
Enter_the_Application_Id_Here
: L'ID applicazione (client) dell'applicazione che hai registrato è. -
Enter_the_Client_secret
: sostituire questo valore con il segreto client creato in precedenza. Per generare una nuova chiave, usare Certificati & segreti nelle impostazioni di registrazione dell'app nel portale di Azure.
Avvertimento
Qualsiasi segreto di testo non crittografato nel codice sorgente comporta un aumento del rischio di sicurezza. Questo articolo usa un segreto client in testo non crittografato solo per semplicità. Usare le credenziali del certificato anziché i segreti dei client nelle applicazioni client riservate, soprattutto quelle che si intendono distribuire nell'ambiente di produzione.
-
Enter_the_Graph_Endpoint_Here
: l'istanza cloud dell'API Microsoft Graph che verrà chiamata dall'app. Per il servizio principale (globale) dell'API Microsoft Graph, immetterehttps://graph.microsoft.com/
(includere la barra rovesciata finale). -
Enter_the_Express_Session_Secret_Here
il segreto usato per firmare il cookie di sessione Express. Scegliere una stringa casuale di caratteri con cui sostituire questa stringa, ad esempio il segreto client.
- Creare quindi un file denominato authConfig.js nella radice del progetto per la lettura in questi parametri. Dopo la creazione, aggiungere il codice seguente:
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
require('dotenv').config({ path: '.env.dev' });
/**
* Configuration object to be passed to MSAL instance on creation.
* For a full list of MSAL Node configuration parameters, visit:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/configuration.md
*/
const msalConfig = {
auth: {
clientId: process.env.CLIENT_ID, // 'Application (client) ID' of app registration in Azure portal - this value is a GUID
authority: process.env.CLOUD_INSTANCE + process.env.TENANT_ID, // Full directory URL, in the form of https://login.microsoftonline.com/<tenant>
clientSecret: process.env.CLIENT_SECRET // Client secret generated from the app registration in Azure portal
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: 3,
}
}
}
const REDIRECT_URI = process.env.REDIRECT_URI;
const POST_LOGOUT_REDIRECT_URI = process.env.POST_LOGOUT_REDIRECT_URI;
const GRAPH_ME_ENDPOINT = process.env.GRAPH_API_ENDPOINT + "v1.0/me";
module.exports = {
msalConfig,
REDIRECT_URI,
POST_LOGOUT_REDIRECT_URI,
GRAPH_ME_ENDPOINT
};
Aggiungere codice per l'accesso utente e l'acquisizione di token
- Creare una nuova cartella denominata authe aggiungere un nuovo file denominato AuthProvider.js sotto di esso. Questo conterrà la classe AuthProvider, che incapsula la logica di autenticazione necessaria usando il nodo MSAL. Aggiungere il codice seguente:
const msal = require('@azure/msal-node');
const axios = require('axios');
const { msalConfig } = require('../authConfig');
class AuthProvider {
msalConfig;
cryptoProvider;
constructor(msalConfig) {
this.msalConfig = msalConfig
this.cryptoProvider = new msal.CryptoProvider();
};
login(options = {}) {
return async (req, res, next) => {
/**
* MSAL Node library allows you to pass your custom state as state parameter in the Request object.
* 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.
*/
const state = this.cryptoProvider.base64Encode(
JSON.stringify({
successRedirect: options.successRedirect || '/',
})
);
const authCodeUrlRequestParams = {
state: state,
/**
* By default, MSAL Node will add OIDC scopes to the auth code url request. For more information, visit:
* https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
*/
scopes: options.scopes || [],
redirectUri: options.redirectUri,
};
const authCodeRequestParams = {
state: state,
/**
* By default, MSAL Node will add OIDC scopes to the auth code request. For more information, visit:
* https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
*/
scopes: options.scopes || [],
redirectUri: options.redirectUri,
};
/**
* If the current msal configuration does not have cloudDiscoveryMetadata or authorityMetadata, we will
* make a request to the relevant endpoints to retrieve the metadata. This allows MSAL to avoid making
* metadata discovery calls, thereby improving performance of token acquisition process. For more, see:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/performance.md
*/
if (!this.msalConfig.auth.cloudDiscoveryMetadata || !this.msalConfig.auth.authorityMetadata) {
const [cloudDiscoveryMetadata, authorityMetadata] = await Promise.all([
this.getCloudDiscoveryMetadata(this.msalConfig.auth.authority),
this.getAuthorityMetadata(this.msalConfig.auth.authority)
]);
this.msalConfig.auth.cloudDiscoveryMetadata = JSON.stringify(cloudDiscoveryMetadata);
this.msalConfig.auth.authorityMetadata = JSON.stringify(authorityMetadata);
}
const msalInstance = this.getMsalInstance(this.msalConfig);
// trigger the first leg of auth code flow
return this.redirectToAuthCodeUrl(
authCodeUrlRequestParams,
authCodeRequestParams,
msalInstance
)(req, res, next);
};
}
acquireToken(options = {}) {
return async (req, res, next) => {
try {
const msalInstance = this.getMsalInstance(this.msalConfig);
/**
* If a token cache exists in the session, deserialize it and set it as the
* cache for the new MSAL CCA instance. For more, see:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/caching.md
*/
if (req.session.tokenCache) {
msalInstance.getTokenCache().deserialize(req.session.tokenCache);
}
const tokenResponse = await msalInstance.acquireTokenSilent({
account: req.session.account,
scopes: options.scopes || [],
});
/**
* On successful token acquisition, write the updated token
* cache back to the session. For more, see:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/caching.md
*/
req.session.tokenCache = msalInstance.getTokenCache().serialize();
req.session.accessToken = tokenResponse.accessToken;
req.session.idToken = tokenResponse.idToken;
req.session.account = tokenResponse.account;
res.redirect(options.successRedirect);
} catch (error) {
if (error instanceof msal.InteractionRequiredAuthError) {
return this.login({
scopes: options.scopes || [],
redirectUri: options.redirectUri,
successRedirect: options.successRedirect || '/',
})(req, res, next);
}
next(error);
}
};
}
handleRedirect(options = {}) {
return async (req, res, next) => {
if (!req.body || !req.body.state) {
return next(new Error('Error: response not found'));
}
const authCodeRequest = {
...req.session.authCodeRequest,
code: req.body.code,
codeVerifier: req.session.pkceCodes.verifier,
};
try {
const msalInstance = this.getMsalInstance(this.msalConfig);
if (req.session.tokenCache) {
msalInstance.getTokenCache().deserialize(req.session.tokenCache);
}
const tokenResponse = await msalInstance.acquireTokenByCode(authCodeRequest, req.body);
req.session.tokenCache = msalInstance.getTokenCache().serialize();
req.session.idToken = tokenResponse.idToken;
req.session.account = tokenResponse.account;
req.session.isAuthenticated = true;
const state = JSON.parse(this.cryptoProvider.base64Decode(req.body.state));
res.redirect(state.successRedirect);
} catch (error) {
next(error);
}
}
}
logout(options = {}) {
return (req, res, next) => {
/**
* Construct a logout URI and redirect the user to end the
* session with Azure AD. For more information, visit:
* https://docs.microsoft.com/azure/active-directory/develop/v2-protocols-oidc#send-a-sign-out-request
*/
let logoutUri = `${this.msalConfig.auth.authority}/oauth2/v2.0/`;
if (options.postLogoutRedirectUri) {
logoutUri += `logout?post_logout_redirect_uri=${options.postLogoutRedirectUri}`;
}
req.session.destroy(() => {
res.redirect(logoutUri);
});
}
}
/**
* Instantiates a new MSAL ConfidentialClientApplication object
* @param msalConfig: MSAL Node Configuration object
* @returns
*/
getMsalInstance(msalConfig) {
return new msal.ConfidentialClientApplication(msalConfig);
}
/**
* Prepares the auth code request parameters and initiates the first leg of auth code flow
* @param req: Express request object
* @param res: Express response object
* @param next: Express next function
* @param authCodeUrlRequestParams: parameters for requesting an auth code url
* @param authCodeRequestParams: parameters for requesting tokens using auth code
*/
redirectToAuthCodeUrl(authCodeUrlRequestParams, authCodeRequestParams, msalInstance) {
return async (req, res, next) => {
// Generate PKCE Codes before starting the authorization flow
const { verifier, challenge } = await this.cryptoProvider.generatePkceCodes();
// Set generated PKCE codes and method as session vars
req.session.pkceCodes = {
challengeMethod: 'S256',
verifier: verifier,
challenge: challenge,
};
/**
* By manipulating the request objects below before each request, we can obtain
* auth artifacts with desired claims. For more information, visit:
* https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_node.html#authorizationurlrequest
* https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_node.html#authorizationcoderequest
**/
req.session.authCodeUrlRequest = {
...authCodeUrlRequestParams,
responseMode: msal.ResponseMode.FORM_POST, // recommended for confidential clients
codeChallenge: req.session.pkceCodes.challenge,
codeChallengeMethod: req.session.pkceCodes.challengeMethod,
};
req.session.authCodeRequest = {
...authCodeRequestParams,
code: '',
};
try {
const authCodeUrlResponse = await msalInstance.getAuthCodeUrl(req.session.authCodeUrlRequest);
res.redirect(authCodeUrlResponse);
} catch (error) {
next(error);
}
};
}
/**
* Retrieves cloud discovery metadata from the /discovery/instance endpoint
* @returns
*/
async getCloudDiscoveryMetadata(authority) {
const endpoint = 'https://login.microsoftonline.com/common/discovery/instance';
try {
const response = await axios.get(endpoint, {
params: {
'api-version': '1.1',
'authorization_endpoint': `${authority}/oauth2/v2.0/authorize`
}
});
return await response.data;
} catch (error) {
throw error;
}
}
/**
* Retrieves oidc metadata from the openid endpoint
* @returns
*/
async getAuthorityMetadata(authority) {
const endpoint = `${authority}/v2.0/.well-known/openid-configuration`;
try {
const response = await axios.get(endpoint);
return await response.data;
} catch (error) {
console.log(error);
}
}
}
const authProvider = new AuthProvider(msalConfig);
module.exports = authProvider;
- Creare quindi un nuovo file denominato auth.js nella route cartella e aggiungere il codice seguente:
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
var express = require('express');
const authProvider = require('../auth/AuthProvider');
const { REDIRECT_URI, POST_LOGOUT_REDIRECT_URI } = require('../authConfig');
const router = express.Router();
router.get('/signin', authProvider.login({
scopes: [],
redirectUri: REDIRECT_URI,
successRedirect: '/'
}));
router.get('/acquireToken', authProvider.acquireToken({
scopes: ['User.Read'],
redirectUri: REDIRECT_URI,
successRedirect: '/users/profile'
}));
router.post('/redirect', authProvider.handleRedirect());
router.get('/signout', authProvider.logout({
postLogoutRedirectUri: POST_LOGOUT_REDIRECT_URI
}));
module.exports = router;
- Aggiornare la route index.js sostituendo il codice esistente con il frammento di codice seguente:
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
var express = require('express');
var router = express.Router();
router.get('/', function (req, res, next) {
res.render('index', {
title: 'MSAL Node & Express Web App',
isAuthenticated: req.session.isAuthenticated,
username: req.session.account?.username,
});
});
module.exports = router;
- Aggiornare infine la route users.js sostituendo il codice esistente con il frammento di codice seguente:
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
var express = require('express');
var router = express.Router();
var fetch = require('../fetch');
var { GRAPH_ME_ENDPOINT } = require('../authConfig');
// custom middleware to check auth state
function isAuthenticated(req, res, next) {
if (!req.session.isAuthenticated) {
return res.redirect('/auth/signin'); // redirect to sign-in route
}
next();
};
router.get('/id',
isAuthenticated, // check if user is authenticated
async function (req, res, next) {
res.render('id', { idTokenClaims: req.session.account.idTokenClaims });
}
);
router.get('/profile',
isAuthenticated, // check if user is authenticated
async function (req, res, next) {
try {
const graphResponse = await fetch(GRAPH_ME_ENDPOINT, req.session.accessToken);
res.render('profile', { profile: graphResponse });
} catch (error) {
next(error);
}
}
);
module.exports = router;
Aggiungere codice per chiamare l'API Microsoft Graph
Creare un file denominato fetch.js nella radice del progetto e aggiungere il codice seguente:
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
var axios = require('axios');
/**
* Attaches a given access token to a MS Graph API call
* @param endpoint: REST API endpoint to call
* @param accessToken: raw access token string
*/
async function fetch(endpoint, accessToken) {
const options = {
headers: {
Authorization: `Bearer ${accessToken}`
}
};
console.log(`request made to ${endpoint} at: ` + new Date().toString());
try {
const response = await axios.get(endpoint, options);
return await response.data;
} catch (error) {
throw new Error(error);
}
}
module.exports = fetch;
Aggiungere visualizzazioni per visualizzare i dati
- Nella cartella viste, aggiornare il file index.hbs sostituendo il codice esistente con il seguente.
<h1>{{title}}</h1>
{{#if isAuthenticated }}
<p>Hi {{username}}!</p>
<a href="/users/id">View ID token claims</a>
<br>
<a href="/auth/acquireToken">Acquire a token to call the Microsoft Graph API</a>
<br>
<a href="/auth/signout">Sign out</a>
{{else}}
<p>Welcome to {{title}}</p>
<a href="/auth/signin">Sign in</a>
{{/if}}
- Sempre nella stessa cartella creare un altro file denominato id.hbs per visualizzare il contenuto del token ID dell'utente:
<h1>Azure AD</h1>
<h3>ID Token</h3>
<table>
<tbody>
{{#each idTokenClaims}}
<tr>
<td>{{@key}}</td>
<td>{{this}}</td>
</tr>
{{/each}}
</tbody>
</table>
<br>
<a href="https://aka.ms/id-tokens" target="_blank">Learn about claims in this ID token</a>
<br>
<a href="/">Go back</a>
- Creare infine un altro file denominato profile.hbs per visualizzare il risultato della chiamata effettuata a Microsoft Graph:
<h1>Microsoft Graph API</h1>
<h3>/me endpoint response</h3>
<table>
<tbody>
{{#each profile}}
<tr>
<td>{{@key}}</td>
<td>{{this}}</td>
</tr>
{{/each}}
</tbody>
</table>
<br>
<a href="/">Go back</a>
Registrare i router e aggiungere la gestione dello stato
Nel file app.js nella radice della cartella del progetto registrare le route create in precedenza e aggiungere il supporto della sessione per tenere traccia dello stato di autenticazione usando il pacchetto sessione rapida. Sostituire il codice esistente con il frammento di codice seguente:
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
require('dotenv').config();
var path = require('path');
var express = require('express');
var session = require('express-session');
var createError = require('http-errors');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var authRouter = require('./routes/auth');
// initialize express
var app = express();
/**
* Using express-session middleware for persistent user session. Be sure to
* familiarize yourself with available options. Visit: https://www.npmjs.com/package/express-session
*/
app.use(session({
secret: process.env.EXPRESS_SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: false, // set this to true on production
}
}));
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');
app.use(logger('dev'));
app.use(express.json());
app.use(cookieParser());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/auth', authRouter);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
Testare l'accesso e chiamare Microsoft Graph
La creazione dell'applicazione è stata completata ed è ora possibile testare le funzionalità dell'app.
- Avviare l'app console Node.js eseguendo il comando seguente dalla radice della cartella del progetto:
npm start
- Aprire una finestra del browser e passare a
http://localhost:3000
. Verrà visualizzata una pagina di benvenuto:
- Selezionare il link Accedi. Verrà visualizzata la schermata di accesso a Microsoft Entra:
- Dopo aver immesso le credenziali, verrà visualizzata una schermata di consenso che chiede di approvare le autorizzazioni per l'app.
- Dopo aver fornito il consenso, si dovrebbe essere reindirizzati alla home page dell'applicazione.
- Selezionare il collegamento View ID Token (Visualizza token ID) per visualizzare il contenuto del token ID dell'utente connesso.
schermata
- Torna alla home page e seleziona il collegamento Acquisire un token di accesso e chiamare l'API Microsoft Graph. Dopo aver eseguito questa operazione, verrà visualizzata la risposta dell'endpoint di Microsoft Graph /me per l'utente connesso.
- Tornare alla home page e selezionare il collegamento Disconnetti. Dovresti vedere la schermata di disconnessione di Microsoft Entra.
Funzionamento dell'applicazione
In questa esercitazione è stata creata un'istanza di un oggetto MSAL Node ConfidentialClientApplication passandogli un oggetto di configurazione (msalConfig) che contiene i parametri ottenuti dalla registrazione dell'app nel portale di Azure di Microsoft Entra. L'app Web che hai creato utilizza il protocollo OpenID Connect per effettuare l'accesso degli utenti e il flusso del codice di autorizzazione OAuth 2.0 per ottenere i token di accesso.
Passaggi successivi
Se desideri approfondire lo sviluppo di applicazioni web Node.js & Express sulla piattaforma di identità Microsoft, consulta la nostra serie di scenari articolata in più parti.