Esercitazione: Accedere agli utenti e acquisire un token per Microsoft Graph in un'app Web Node.js & Express

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 il nodo MSAL per accedere, disconnettersi e acquisire un token di accesso per una risorsa protetta, ad esempio Microsoft Graph.

Prerequisiti

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 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: viene visualizzato una sola volta)

Creare il progetto

Usare lo strumento generatore di applicazioni Express per creare uno scheletro dell'applicazione.

  1. Installare prima di tutto il pacchetto express-generator :
    npm install -g express-generator
  1. 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

Individuare la radice della directory del progetto in un terminale e installare il pacchetto del nodo MSAL 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, il 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

  1. Creare un file con estensione 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: l'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 finale).
    • Peri cloud nazionali, ad esempio Cina, è possibile trovare i valori appropriati nella pagina Cloud nazionali.
  • Enter_the_Tenant_Info_here deve essere uno dei parametri seguenti:
    • Se l'applicazione supporta 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 account in qualsiasi directory organizzativa, sostituire questo valore con organizations.
    • Se l'applicazione supporta account in qualsiasi directory organizzativa e account Microsoft personali, sostituire questo valore con common.
    • Per limitare il supporto ai soli account Microsoft personali, sostituire questo valore con consumers.
  • Enter_the_Application_Id_Here: ID applicazione (client) dell'applicazione registrata.
  • Enter_the_Client_secret: sostituire questo valore con il segreto client creato in precedenza. Per generare una nuova chiave, usare Certificati e segreti nelle impostazioni di registrazione dell'app nel portale di Azure.

Avviso

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 client nelle applicazioni client riservate, in particolare quelle che si intende 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 API Microsoft Graph principale (globale), immettere https://graph.microsoft.com/ (includere la barra finale).
  • Enter_the_Express_Session_Secret_Here segreto utilizzato per firmare il cookie di sessione Express. Scegliere una stringa casuale di caratteri con cui sostituire questa stringa, ad esempio il segreto client.
  1. 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

  1. Creare una nuova cartella denominata auth e 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;
  1. Creare quindi un nuovo file denominato auth.js nella cartella routes 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;
  1. 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;
  1. 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 la visualizzazione dei dati

  1. Nella cartella views aggiornare il file index.hbs sostituendo il codice esistente con il codice 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}}
  1. 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>
  1. 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 express-session. 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.

  1. Avviare l'app console Node.js eseguendo il comando seguente dalla radice della cartella del progetto:
   npm start
  1. Aprire una finestra del browser e passare a http://localhost:3000. Verrà visualizzata una pagina di benvenuto:

Web app welcome page displaying

  1. Selezionare Il collegamento Accedi . Verrà visualizzata la schermata di accesso a Microsoft Entra:

Microsoft Entra sign-in screen displaying

  1. Dopo aver immesso le credenziali, verrà visualizzata una schermata di consenso che chiede di approvare le autorizzazioni per l'app.

Microsoft Entra consent screen displaying

  1. Dopo aver fornito il consenso, si dovrebbe essere reindirizzati alla home page dell'applicazione.

Web app welcome page after sign-in displaying

  1. Selezionare il collegamento Visualizza token ID per visualizzare il contenuto del token ID dell'utente connesso.

User ID token screen displaying

  1. Tornare alla home page e selezionare il collegamento Acquisire un token di accesso e chiamare il collegamento API Microsoft Graph. Dopo aver eseguito questa operazione, verrà visualizzata la risposta dell'endpoint di Microsoft Graph /me per l'utente connesso.

Graph call screen displaying

  1. Tornare alla home page e selezionare il collegamento Disconnetti . Verrà visualizzata la schermata di disconnesso Microsoft Entra.

Microsoft Entra sign-out screen displaying

Funzionamento dell'applicazione

In questa esercitazione è stata creata un'istanza di un oggetto ConfidentialClientApplication del nodo MSAL passandolo a un oggetto di configurazione (msalConfig) che contiene i parametri ottenuti dalla registrazione dell'app Microsoft Entra in portale di Azure. L'app Web creata usa il protocollo OpenID Connessione per accedere agli utenti e al flusso del codice di autorizzazione OAuth 2.0 per ottenere i token di accesso.

Passaggi successivi

Per approfondire lo sviluppo di applicazioni Web Node.js e Express in Microsoft Identity Platform, vedere la serie di scenari in più parti: