Kurz: Přihlášení uživatelů a získání tokenu pro Microsoft Graph ve webové aplikaci Node.js & Express
V tomto kurzu vytvoříte webovou aplikaci, která přihlásí uživatele a získá přístupové tokeny pro volání Microsoft Graphu. Webová aplikace, kterou sestavíte, používá knihovnu MICROSOFT Authentication Library (MSAL) pro Node.
Postupujte podle kroků v tomto kurzu:
- Registrace aplikace na webu Azure Portal
- Vytvoření projektu webové aplikace Express
- Instalace balíčků ověřovací knihovny
- Přidání podrobností o registraci aplikace
- Přidání kódu pro přihlášení uživatele
- Otestování aplikace
Další informace najdete v ukázkovém kódu , který ukazuje, jak se pomocí uzlu MSAL přihlásit, odhlásit se a získat přístupový token pro chráněný prostředek, jako je Microsoft Graph.
Požadavky
- Node.js
- Visual Studio Code nebo jiný editor kódu
Registrace aplikace
Nejprve proveďte kroky v části Registrace aplikace na platformě Microsoft Identity Platform a zaregistrujte aplikaci.
Pro registraci aplikace použijte následující nastavení:
- Název:
ExpressWebApp
(navrhované) - Podporované typy účtů: Účty pouze v tomto organizačním adresáři
- Typ platformy: Web
- Identifikátor URI přesměrování:
http://localhost:3000/auth/redirect
- Tajný klíč klienta:
*********
(poznamenejte si tuto hodnotu pro použití v pozdějším kroku – zobrazí se jenom jednou).
Vytvoření projektu
Pomocí nástroje generátoru aplikací Express vytvořte kostru aplikace.
- Nejprve nainstalujte balíček express-generator :
npm install -g express-generator
- Pak vytvořte kostru aplikace následujícím způsobem:
express --view=hbs /ExpressWebApp && cd /ExpressWebApp
npm install
Teď máte jednoduchou webovou aplikaci Express. Struktura souborů a složek projektu by měla vypadat podobně jako následující struktura složek:
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
Instalace knihovny ověřování
Vyhledejte kořen adresáře projektu v terminálu a nainstalujte balíček MSAL Node přes npm.
npm install --save @azure/msal-node
Instalace dalších závislostí
Ukázka webové aplikace v tomto kurzu používá balíček express-session pro správu relací, balíček dotenv pro čtení parametrů prostředí během vývoje a axios pro volání sítě do rozhraní Microsoft Graph API. Nainstalujte je prostřednictvím npm:
npm install --save express-session dotenv axios
Přidání podrobností o registraci aplikace
- V kořenové složce projektu vytvořte soubor .env.dev . Pak přidejte následující kód:
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"
Vyplňte tyto podrobnosti hodnotami, které získáte z portálu pro registraci aplikací Azure:
Enter_the_Cloud_Instance_Id_Here
: Cloudová instance Azure, ve které je vaše aplikace zaregistrovaná.- V případě hlavního (nebo globálního) cloudu Azure zadejte
https://login.microsoftonline.com/
(včetně koncového lomítka). - Pro národní cloudy (například Čína) najdete odpovídající hodnoty v národních cloudech.
- V případě hlavního (nebo globálního) cloudu Azure zadejte
Enter_the_Tenant_Info_here
by měl být jedním z následujících parametrů:- Pokud vaše aplikace podporuje účty v tomto organizačním adresáři, nahraďte tuto hodnotu ID tenanta nebo názvem tenanta. Například
contoso.microsoft.com
. - Pokud vaše aplikace podporuje účty v libovolném organizačním adresáři, nahraďte tuto hodnotu
organizations
hodnotou . - Pokud vaše aplikace podporuje účty v libovolném organizačním adresáři a osobních účtech Microsoft, nahraďte tuto hodnotu
common
hodnotou . - Chcete-li omezit podporu pouze na osobní účty Microsoft, nahraďte tuto hodnotu
consumers
hodnotou .
- Pokud vaše aplikace podporuje účty v tomto organizačním adresáři, nahraďte tuto hodnotu ID tenanta nebo názvem tenanta. Například
Enter_the_Application_Id_Here
: ID aplikace (klienta) aplikace, kterou jste zaregistrovali.Enter_the_Client_secret
: Tuto hodnotu nahraďte tajným kódem klienta, který jste vytvořili dříve. Pokud chcete vygenerovat nový klíč, použijte certifikáty a tajné kódy v nastavení registrace aplikace na webu Azure Portal.
Upozorňující
Jakýkoli tajný kód prostého textu ve zdrojovém kódu představuje zvýšené bezpečnostní riziko. Tento článek používá pouze tajný kód klienta ve formátu prostého textu. V důvěrných klientských aplikacích používejte přihlašovací údaje certifikátu místo tajných klíčů klienta, zejména ty aplikace, které chcete nasadit do produkčního prostředí.
Enter_the_Graph_Endpoint_Here
: Cloudová instance rozhraní Microsoft Graph API, kterou bude vaše aplikace volat. Pro hlavní (globální) službu Microsoft Graph API zadejtehttps://graph.microsoft.com/
(včetně koncového lomítka).Enter_the_Express_Session_Secret_Here
tajný kód použitý k podepsání souboru cookie relace Expressu. Zvolte náhodný řetězec znaků, kterým chcete tento řetězec nahradit, například tajným kódem klienta.
- Dále vytvořte soubor s názvem authConfig.js v kořenovém adresáři projektu pro čtení v těchto parametrech. Po vytvoření přidejte do ní následující kód:
/*
* 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
};
Přidání kódu pro získání přihlášení uživatele a tokenu
- Vytvořte novou složku s názvem Ověřování a přidejte do ní nový soubor s názvem AuthProvider.js . Bude obsahovat třídu AuthProvider , která zapouzdřuje potřebnou logiku ověřování pomocí uzlu MSAL. Přidejte do ní následující kód:
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;
- Dále vytvořte nový soubor s názvem auth.js ve složce routes a přidejte do ní následující kód:
/*
* 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;
- Aktualizujte trasu index.js nahrazením existujícího kódu následujícím fragmentem kódu:
/*
* 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;
- Nakonec aktualizujte trasu users.js nahrazením existujícího kódu následujícím fragmentem kódu:
/*
* 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;
Přidání kódu pro volání rozhraní Microsoft Graph API
V kořenovém adresáři projektu vytvořte soubor s názvem fetch.js a přidejte následující kód:
/*
* 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;
Přidání zobrazení pro zobrazení dat
- Ve složce zobrazení aktualizujte soubor index.hbs nahrazením existujícího kódu následujícím kódem:
<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}}
- Ve stejné složce vytvořte další soubor s názvem id.hbs pro zobrazení obsahu tokenu ID uživatele:
<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>
- Nakonec vytvořte další soubor profile.hbs pro zobrazení výsledku volání microsoft Graphu:
<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>
Registrace směrovačů a přidání správy stavu
V souboru app.js v kořenové složce projektu zaregistrujte trasy, které jste vytvořili dříve, a přidejte podporu relace pro sledování stavu ověřování pomocí balíčku express-session. Existující kód tam nahraďte následujícím fragmentem kódu:
/*
* 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;
Testování přihlášení a volání Microsoft Graphu
Dokončili jste vytváření aplikace a teď jste připraveni otestovat funkčnost aplikace.
- Spusťte konzolovou aplikaci Node.js spuštěním následujícího příkazu v kořenové složce projektu:
npm start
- Otevřete okno prohlížeče a přejděte na adresu
http://localhost:3000
. Měla by se zobrazit úvodní stránka:
- Vyberte odkaz Přihlásit se . Měla by se zobrazit přihlašovací obrazovka Microsoft Entra:
- Po zadání přihlašovacích údajů by se měla zobrazit obrazovka pro vyjádření souhlasu s žádostí o schválení oprávnění pro aplikaci.
- Jakmile souhlasíte, měli byste být přesměrováni zpět na domovskou stránku aplikace.
- Vyberte odkaz Token ID zobrazení pro zobrazení obsahu tokenu ID přihlášeného uživatele.
- Vraťte se na domovskou stránku a vyberte přístupový token Získat a zavolejte odkaz rozhraní Microsoft Graph API . Jakmile to uděláte, měla by se zobrazit odpověď koncového bodu Microsoft Graph /me pro přihlášeného uživatele.
- Vraťte se na domovskou stránku a vyberte odkaz Odhlásit se. Měla by se zobrazit přihlašovací obrazovka Microsoft Entra.
Jak aplikace funguje
V tomto kurzu jste vytvořili instanci objektu MSAL Node ConfidentialClientApplication předáním objektu konfigurace (msalConfig), který obsahuje parametry získané z vaší registrace aplikace Microsoft Entra na webu Azure Portal. Vytvořená webová aplikace používá protokol OpenID Připojení pro přihlášení uživatelů a tok autorizačního kódu OAuth 2.0 k získání přístupových tokenů.
Další kroky
Pokud se chcete podrobněji podívat na vývoj webových aplikací Node.js & Express na platformě Microsoft Identity Platform, podívejte se na naši sérii scénářů s více částmi: