Tutorial: Memasukkan pengguna dan memperoleh token untuk Microsoft Graph di aplikasi web Node.js & Express
Dalam tutorial ini, Anda membuat aplikasi web yang memasukkan pengguna dan memperoleh token akses untuk memanggil Microsoft Graph. Aplikasi web yang Anda buat menggunakan Microsoft Authentication Library (MSAL) untuk Simpul.
Ikuti langkah-langkah dalam tutorial ini untuk:
- Mendaftarkan aplikasi di portal Microsoft Azure
- Membuat proyek aplikasi web Express
- Memasang paket pustaka autentikasi
- Menambahkan detail pendaftaran aplikasi
- Menambahkan kode untuk proses masuk pengguna
- Menguji aplikasi
Untuk informasi selengkapnya, lihat kode sampel yang menunjukkan cara menggunakan MSAL Node untuk masuk, keluar, dan memperoleh token akses untuk sumber daya yang dilindungi seperti Microsoft Graph.
Prasyarat
- Node.js
- Visual Studio Code atau editor kode lainnya
Mendaftarkan aplikasi
Pertama, selesaikan langkah-langkah dalam Mendaftarkan aplikasi dengan platform identitas Microsoft untuk mendaftarkan aplikasi Anda.
Gunakan pengaturan berikut untuk pendaftaran aplikasi Anda:
- Nama:
ExpressWebApp
(disarankan) - Jenis akun yang didukung: Hanya akun dalam direktori organisasi ini
- Jenis platform: Web
- URI Pengalihan:
http://localhost:3000/auth/redirect
- Rahasia klien:
*********
(catat nilai ini untuk digunakan pada langkah selanjutnya - hanya ditampilkan sekali)
Membuat proyek
Gunakan alat generator aplikasi Ekspres untuk membuat kerangka aplikasi.
- Pertama, instal paket generator ekspres:
npm install -g express-generator
- Kemudian, buat kerangka aplikasi sebagai berikut:
express --view=hbs /ExpressWebApp && cd /ExpressWebApp
npm install
Anda sekarang memiliki aplikasi web Ekspres sederhana. Struktur file dan folder proyek Anda akan terlihat mirip dengan struktur folder berikut:
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
Menginstal pustaka auth
Cari akar direktori proyek di terminal dan instal paket MSAL Node melalui NPM.
npm install --save @azure/msal-node
Instal dependensi lainnya
Sampel aplikasi web dalam tutorial ini menggunakan paket sesi ekspres untuk manajemen sesi, paket dotenv untuk membaca parameter lingkungan selama pengembangan, dan axios untuk melakukan panggilan jaringan ke Microsoft Graph API. Instal ini melalui npm:
npm install --save express-session dotenv axios
Menambahkan detail pendaftaran aplikasi
- Buat file .env.dev di akar folder proyek Anda. Tambahkan kode berikut:
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"
Isi detail berikut dengan nilai yang diperoleh dari portal pendaftaran aplikasi Azure:
Enter_the_Cloud_Instance_Id_Here
: Instans cloud Azure tempat aplikasi Anda terdaftar.- Untuk cloud Azure utama (atau global), masukkan
https://login.microsoftonline.com/
(sertakan garis miring berikutnya). - Untuk cloud nasional (misalnya, Tiongkok), Anda dapat menemukan nilai yang sesuai di Cloud nasional.
- Untuk cloud Azure utama (atau global), masukkan
Enter_the_Tenant_Info_here
harus menjadi salah satu parameter berikut:- Jika aplikasi Anda mendukung akun di direktori organisasi ini, ganti nilai ini dengan ID Penyewa atau nama Penyewa. Contohnya:
contoso.microsoft.com
- Jika aplikasi Anda mendukung akun di direktori organisasi apa pun, ganti nilai ini dengan
organizations
. - Jika aplikasi Anda mendukung akun di direktori organisasi dan akun Microsoft pribadi apa pun, ganti nilai ini dengan
common
. - Untuk membatasi dukungan hanya untuk akun Microsoft pribadi, ganti nilai ini dengan
consumers
.
- Jika aplikasi Anda mendukung akun di direktori organisasi ini, ganti nilai ini dengan ID Penyewa atau nama Penyewa. Contohnya:
Enter_the_Application_Id_Here
: ID Aplikasi (klien) dari aplikasi yang Anda daftarkan.Enter_the_Client_secret
: Ganti nilai ini dengan rahasia klien yang Anda buat sebelumnya. Untuk membuat kunci baru, gunakan Sertifikat & rahasia di pengaturan pendaftaran aplikasi di portal Microsoft Azure.
Peringatan
Setiap rahasia teks biasa dalam kode sumber menimbulkan peningkatan risiko keamanan. Artikel ini menggunakan rahasia klien teks biasa hanya untuk mempermudah. Gunakan kredensial sertifikat, bukan rahasia dalam aplikasi klien rahasia Anda, terutama aplikasi yang ingin Anda sebarkan ke produksi.
Enter_the_Graph_Endpoint_Here
: Instans cloud Microsoft Graph API yang akan dipanggil aplikasi Anda. Untuk layanan Microsoft Graph API utama (global), masukkanhttps://graph.microsoft.com/
(sertakan garis miring berikutnya).Enter_the_Express_Session_Secret_Here
rahasia yang digunakan untuk menandatangani cookie sesi Ekspres. Pilih string karakter acak untuk mengganti string ini, seperti rahasia klien Anda.
- Selanjutnya, buat file bernama authConfig.js di akar proyek Anda untuk dibaca dalam parameter ini. Setelah dibuat, tambahkan kode berikut di sana:
/*
* 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
};
Menambahkan kode untuk masuk pengguna dan akuisisi token
- Buat folder baru bernama auth, dan tambahkan file baru bernama AuthProvider.js di bawahnya. Ini akan berisi kelas AuthProvider , yang merangkum logika autentikasi yang diperlukan menggunakan MSAL Node. Tambahkan kode berikut di sana:
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;
- Selanjutnya, buat file baru bernama auth.js di bawah folder rute dan tambahkan kode berikut di sana:
/*
* 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;
- Perbarui rute index.js dengan mengganti kode yang ada dengan cuplikan kode berikut:
/*
* 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;
- Terakhir, perbarui rute users.js dengan mengganti kode yang ada dengan cuplikan kode berikut:
/*
* 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;
Tambahkan kode untuk memanggil Microsoft Graph API
Buat file bernama fetch.js di akar proyek Anda dan tambahkan kode berikut ke dalamnya:
/*
* 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;
Tambahkan tampilan untuk menampilkan data
- Di folder tampilan, perbarui file index.hbs dengan mengganti kode yang ada dengan yang berikut ini:
<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}}
- Masih di folder yang sama, buat file lain bernama id.hbs untuk menampilkan konten token ID pengguna:
<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>
- Terakhir, buat file lain bernama profile.hbs untuk menampilkan hasil panggilan yang dilakukan ke 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>
Daftarkan rute dan tambahkan manajemen status
Dalam file app.js di akar folder proyek, daftarkan rute yang telah Anda buat sebelumnya dan tambahkan dukungan sesi untuk melacak status autentikasi menggunakan paket sesi ekspres. Ganti kode yang ada di sana dengan cuplikan kode berikut:
/*
* 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;
Uji masuk dan hubungi Microsoft Graph
Anda telah menyelesaikan pembuatan aplikasi dan sekarang Anda siap untuk menguji fungsi aplikasi.
- Mulai aplikasi konsol Node.js dengan menjalankan perintah berikut dari dalam akar folder proyek:
npm start
- Buka jendela browser dan buka
http://localhost:3000
. Anda akan melihat halaman selamat datang:
- Pilih tautan Masuk. Anda akan melihat layar masuk Microsoft Entra:
- Setelah memasukkan info masuk, Anda akan melihat layar persetujuan yang meminta Anda menyetujui izin untuk aplikasi.
- Setelah menyetujui, Anda harus diarahkan kembali ke beranda aplikasi.
- Pilih tautan Lihat Token ID untuk menampilkan konten token ID pengguna yang masuk.
- Kembali ke halaman beranda, dan pilih tautan Dapatkan token akses dan panggil Microsoft Graph API. Setelah melakukannya, Anda akan melihat respons dari titik akhir Microsoft Graph /me untuk pengguna yang masuk.
- Kembali ke halaman beranda, dan pilih tautan Keluar. Anda akan melihat layar keluar Microsoft Entra.
Cara kerja aplikasi
Dalam tutorial ini, Anda membuat instans objek MSAL Node ConfidentialClientApplication dengan meneruskannya objek konfigurasi (msalConfig) yang berisi parameter yang diperoleh dari pendaftaran aplikasi Microsoft Entra Anda di portal Azure. Aplikasi web yang Anda buat menggunakan protokol Koneksi OpenID untuk memasukkan pengguna dan alur kode otorisasi OAuth 2.0 untuk mendapatkan token akses.
Langkah berikutnya
Jika Anda ingin memahami pengembangan aplikasi web Node.js & Express lebih dalam di platform identitas Microsoft, lihat seri skenario multi-bagian kami: