Zelfstudie: Gebruikers aanmelden en een token verkrijgen voor Microsoft Graph in een Node.js & Express-web-app
In deze zelfstudie bouwt u een web-app waarmee gebruikers worden aangemeld en toegangstokens worden verkregen voor het aanroepen van Microsoft Graph. De web-app die u bouwt, maakt gebruik van de Microsoft Authentication Library (MSAL) voor Node.
Volg de stappen in deze zelfstudie voor het volgende:
- De app registreren in de Azure Portal
- Een Express-web-app-project maken
- De verificatiebibliotheekpakketten installeren
- Registratiegegevens van app toevoegen
- Code toevoegen voor gebruikersaanmelding
- De app testen
Zie de voorbeeldcode die laat zien hoe u MSAL Node gebruikt om u aan te melden, af te melden en een toegangstoken te verkrijgen voor een beveiligde resource, zoals Microsoft Graph.
Vereisten
- Node.js
- Visual Studio Code of een andere code-editor
Registreer de toepassing
Voltooi eerst de stappen in Een toepassing registreren bij het Microsoft identity platform om uw app te registreren.
Gebruik de volgende instellingen voor uw app-registratie:
- Naam:
ExpressWebApp
(voorgesteld) - Ondersteunde accounttypen: alleen accounts in deze organisatiemap
- Platformtype: Web
- URI omleiden:
http://localhost:3000/auth/redirect
- Clientgeheim:
*********
(noteer deze waarde voor gebruik in een latere stap- deze wordt slechts één keer weergegeven)
Het project maken
Gebruik het hulpprogramma Express-toepassingsgenerator om een toepassingskelet te maken.
- Installeer eerst het express-generatorpakket :
npm install -g express-generator
- Maak vervolgens als volgt een toepassingskelet:
express --view=hbs /ExpressWebApp && cd /ExpressWebApp
npm install
U hebt nu een eenvoudige Express-web-app. De bestands- en mapstructuur van uw project moet er ongeveer als volgt uitzien:
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
De verificatiebibliotheek installeren
Zoek de hoofdmap van uw projectmap in een terminal en installeer het MSAL Node-pakket via npm.
npm install --save @azure/msal-node
Andere afhankelijkheden installeren
Het voorbeeld van de web-app in deze zelfstudie maakt gebruik van het express-sessiepakket voor sessiebeheer, dotenv-pakket voor het lezen van omgevingsparameters tijdens de ontwikkeling en axios voor het maken van netwerkoproepen naar de Microsoft Graph API. Installeer deze via npm:
npm install --save express-session dotenv axios
Registratiegegevens van app toevoegen
- Maak een .env.dev-bestand in de hoofdmap van de projectmap. Voeg vervolgens de volgende code toe:
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"
Vul deze gegevens in met de waarden die u hebt verkregen via de registratie-portal van de Azure-app:
Enter_the_Cloud_Instance_Id_Here
: Het Azure-cloudexemplaren waarin uw toepassing is geregistreerd.- Voer
https://login.microsoftonline.com/
voor de hoofdcloud (of globale) Azure-cloud het volgende in (inclusief de afsluitende slash). - Voor nationale clouds (bijvoorbeeld China) kunt u de juiste waarden vinden in Nationale clouds.
- Voer
Enter_the_Tenant_Info_here
moet een van de volgende parameters zijn:- Als uw toepassing ondersteuning biedt voor accounts in deze organisatiemap, vervangt u deze waarde door de Tenant-id of Tenantnaam. Bijvoorbeeld:
contoso.microsoft.com
. - Als uw toepassing ondersteuning biedt voor accounts in elke organisatiemap, vervangt u waarde door
organizations
. - Als uw toepassing accounts in elke organisatiemap en persoonlijke Microsoft-accounts ondersteunt, vervang deze waarde dan door
common
. - Als u de ondersteuning wilt beperken tot alleen persoonlijke Microsoft-accounts, vervang deze waarde dan door
consumers
.
- Als uw toepassing ondersteuning biedt voor accounts in deze organisatiemap, vervangt u deze waarde door de Tenant-id of Tenantnaam. Bijvoorbeeld:
Enter_the_Application_Id_Here
: de toepassings-id (client) van de toepassing die u hebt geregistreerd.Enter_the_Client_secret
: Vervang deze waarde door het clientgeheim dat u eerder hebt gemaakt. Als u een nieuwe sleutel wilt genereren, gebruikt u Certificaten en geheimen in de app-registratie-instellingen in Azure Portal.
Waarschuwing
Elk geheim zonder opmaak in broncode vormt een verhoogd beveiligingsrisico. In dit artikel wordt alleen een clientgeheim zonder opmaak gebruikt. Gebruik certificaatreferenties in plaats van clientgeheimen in uw vertrouwelijke clienttoepassingen, met name de apps die u wilt implementeren in productie.
Enter_the_Graph_Endpoint_Here
: Het Microsoft Graph API-cloudexemplaren die door uw app worden aangeroepen. Voer voor de primaire (globale) Microsoft Graph API-servicehttps://graph.microsoft.com/
in (neem de afsluitende slash op).Enter_the_Express_Session_Secret_Here
het geheim dat wordt gebruikt voor het ondertekenen van de Express-sessiecooky. Kies een willekeurige tekenreeks om deze tekenreeks te vervangen door, zoals uw clientgeheim.
- Maak vervolgens een bestand met de naam authConfig.js in de hoofdmap van uw project voor het lezen in deze parameters. Voeg na het maken de volgende code toe:
/*
* 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
};
Code toevoegen voor het verkrijgen van gebruikersaanmelding en token
- Maak een nieuwe map met de naam auth en voeg een nieuw bestand toe met de naam AuthProvider.js eronder. Dit bevat de AuthProvider-klasse , die de benodigde verificatielogica inkapselt met behulp van MSAL Node. Voeg daar de volgende code toe:
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;
- Maak vervolgens een nieuw bestand met de naam auth.js onder de map routes en voeg daar de volgende code toe:
/*
* 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;
- Werk de index.js route bij door de bestaande code te vervangen door het volgende codefragment:
/*
* 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;
- Werk ten slotte de users.js route bij door de bestaande code te vervangen door het volgende codefragment:
/*
* 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;
Code toevoegen voor het aanroepen van de Microsoft Graph API
Maak een bestand met de naam fetch.js in de hoofdmap van uw project en voeg de volgende code toe:
/*
* 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;
Weergaven toevoegen voor het weergeven van gegevens
- Werk in de map weergaven het bestand index.hbs bij door de bestaande code te vervangen door het volgende:
<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}}
- Maak in dezelfde map nog een bestand met de naam id.hbs voor het weergeven van de inhoud van het id-token van de gebruiker:
<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>
- Maak ten slotte een ander bestand met de naam profile.hbs om het resultaat van de aanroep naar Microsoft Graph weer te geven:
<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>
Routers registreren en statusbeheer toevoegen
Registreer in het app.js-bestand in de hoofdmap van de projectmap de routes die u eerder hebt gemaakt en voeg sessieondersteuning toe voor het bijhouden van de verificatiestatus met behulp van het express-sessiepakket . Vervang de bestaande code daar door het volgende codefragment:
/*
* 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;
Aanmelden testen en Microsoft Graph aanroepen
U hebt het maken van de toepassing voltooid en bent nu klaar om de functionaliteit van de app te testen.
- Start de Node.js console-app door de volgende opdracht uit te voeren vanuit de hoofdmap van uw projectmap:
npm start
- Open een browservenster en ga naar
http://localhost:3000
. U ziet nu een welkomstpagina:
- Selecteer De koppeling Aanmelden . U ziet nu het aanmeldingsscherm van Microsoft Entra:
- Zodra u uw referenties hebt ingevoerd, ziet u een toestemmingsscherm waarin u wordt gevraagd de machtigingen voor de app goed te keuren.
- Zodra u toestemming hebt gegeven, wordt u teruggeleid naar de startpagina van de toepassing.
- Selecteer de koppeling Id-token weergeven om de inhoud van het id-token van de aangemelde gebruiker weer te geven.
- Ga terug naar de startpagina en selecteer het toegangstoken verkrijgen en roep de Microsoft Graph API-koppeling aan. Zodra u dit hebt uitgevoerd, ziet u het antwoord van microsoft Graph/me-eindpunt voor de aangemelde gebruiker.
- Ga terug naar de startpagina en selecteer de koppeling Afmelden . U ziet nu het afmeldingsscherm van Microsoft Entra.
Werking van de toepassing
In deze zelfstudie hebt u een MSAL Node ConfidentialClientApplication-object geïnstantieerd door een configuratieobject (msalConfig) door te geven dat parameters bevat die zijn verkregen uit de registratie van uw Microsoft Entra-app in Azure Portal. De web-app die u hebt gemaakt, maakt gebruik van het OpenID-Verbinding maken-protocol om gebruikers aan te melden en de OAuth 2.0-autorisatiecodestroom om toegangstokens te verkrijgen.
Volgende stappen
Als u meer wilt weten over Node.js & Express-webtoepassingsontwikkeling op het Microsoft Identity Platform, raadpleegt u onze reeks scenario's met meerdere onderdelen: