Delen via


Zelfstudie: Aanmeldingsoptie toevoegen aan een Node/Express.js-web-app met behulp van het Microsoft Identity Platform

Van toepassing op: Een groene cirkel met een wit vinkje. Workforce-huurders Een groene cirkel met een wit vinkje. Externe huurders (meer informatie)

In deze zelfstudie voegt u aanmeldings- en afmeldingslogica toe aan uw Node/Express-web-app. Met deze code kunt u gebruikers aanmelden bij uw klantgerichte app vanuit een externe tenant of werknemers in een personeelstenant.

Deze zelfstudie is deel 2 van de driedelige reeks zelfstudies.

In deze handleiding leert u:

  • Aanmeldings- en afmeldingslogica toevoegen
  • ID-tokenclaims bekijken
  • Voer de app uit en test de aanmeldings- en afmeldingservaring.

Voorwaarden

MSAL-configuratieobject maken

Open authConfig.js bestand in de code-editor en voeg de volgende code toe:

require('dotenv').config();

const TENANT_SUBDOMAIN = process.env.TENANT_SUBDOMAIN || 'Enter_the_Tenant_Subdomain_Here';
const REDIRECT_URI = process.env.REDIRECT_URI || 'http://localhost:3000/auth/redirect';
const POST_LOGOUT_REDIRECT_URI = process.env.POST_LOGOUT_REDIRECT_URI || 'http://localhost:3000';
const GRAPH_ME_ENDPOINT = process.env.GRAPH_API_ENDPOINT + "v1.0/me" || 'Enter_the_Graph_Endpoint_Here';

/**
 * 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 || 'Enter_the_Application_Id_Here', // 'Application (client) ID' of app registration in Azure portal - this value is a GUID
        //For external tenant
        authority: process.env.AUTHORITY || `https://${TENANT_SUBDOMAIN}.ciamlogin.com/`, // replace "Enter_the_Tenant_Subdomain_Here" with your tenant name
        //For workforce tenant
        //authority: process.env.CLOUD_INSTANCE + process.env.TENANT_ID
        clientSecret: process.env.CLIENT_SECRET || 'Enter_the_Client_Secret_Here', // Client secret generated from the app registration in Azure portal
    },
    system: {
        loggerOptions: {
            loggerCallback(loglevel, message, containsPii) {
                console.log(message);
            },
            piiLoggingEnabled: false,
            logLevel: 'Info',
        },
    },
};

module.exports = {
    msalConfig,
    REDIRECT_URI,
    POST_LOGOUT_REDIRECT_URI,
    TENANT_SUBDOMAIN,
    GRAPH_ME_ENDPOINT
};

Het msalConfig-object bevat een set configuratieopties die u gebruikt om het gedrag van uw verificatiestromen aan te passen.

Vervang het volgende in het authConfig.js-bestand:

  • Enter_the_Application_Id_Here met de toepassings-id (client) van de app die u eerder hebt geregistreerd.

  • Enter_the_Tenant_Subdomain_Here en vervang deze door het subdomein van de externe directory (huurder). Als uw primaire tenantdomein bijvoorbeeld is contoso.onmicrosoft.com, gebruikt u contoso. Als u uw tenantnaam niet hebt, leert u hoe u uw tenantgegevens kunt lezen. Deze waarde is alleen vereist voor externe tenant-.

  • Enter_the_Client_Secret_Here met de waarde van het applicatiegeheim die u eerder gekopieerd hebt.

  • Enter_the_Graph_Endpoint_Here met de Microsoft Graph API-cloudinstantie die door uw app wordt aangeroepen. Gebruik de waarde https://graph.microsoft.com/ (inclusief de afsluitende schuine streep)

Als u het bestand .env gebruikt om uw configuratiegegevens op te slaan:

  1. Open in de code-editor .env-bestand en voeg vervolgens de volgende code toe.

        CLIENT_ID=Enter_the_Application_Id_Here
        TENANT_SUBDOMAIN=Enter_the_Tenant_Subdomain_Here 
        CLOUD_INSTANCE="Enter_the_Cloud_Instance_Id_Here" # cloud instance string should end with a trailing slash
        TENANT_ID=Enter_the_Tenant_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 # express session secret, just any random text
    
  2. Vervang de plaatsaanduiding:

    1. Enter_the_Application_Id_Here, Enter_the_Tenant_Subdomain_Here en Enter_the_Client_Secret_Here zoals eerder uitgelegd.
    2. Enter_the_Cloud_Instance_Id_Here bij de Azure-cloudinstantie waarin uw toepassing is geregistreerd. Gebruik https://login.microsoftonline.com/ als waarde (inclusief de afsluitende schuine streep naar rechts). Deze waarde is alleen vereist voor werknemerstenant.
    3. Enter_the_Tenant_ID_here met de tenant-id van het personeel of het primaire domein, zoals aaaabbbb-0000-cccc-1111-dddd2222eeee of contoso.microsoft.com. Deze waarde is alleen vereist voor werknemerstenant.

U exporteert msalConfig, REDIRECT_URI, TENANT_SUBDOMAIN, GRAPH_ME_ENDPOINT en POST_LOGOUT_REDIRECT_URI variabelen in het authConfig.js bestand om ze toegankelijk te maken in andere bestanden.

URL van de authoriteit voor uw app

De toepassingsautoriteiten voor externe tenants en werknemers zien er anders uit. Bouw ze zoals hieronder wordt weergegeven:

//Authority for workforce tenant
authority: process.env.CLOUD_INSTANCE + process.env.TENANT_ID

Aangepast URL-domein gebruiken (optioneel)

Aangepaste URL-domeinen worden niet ondersteund in werkplaats-tenants.

Expressroutes toevoegen

De Express-routes bieden de eindpunten waarmee we bewerkingen kunnen uitvoeren, zoals aanmelden, afmelden en id-tokenclaims weergeven.

App-toegangspunt

Open in de code-editor routes/index.js bestand en voeg vervolgens de volgende code toe:

const express = require('express');
const 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 !== '' ? req.session.account?.username : req.session.account?.name,
    });
});    
module.exports = router;

De / route is het toegangspunt voor de toepassing. Hiermee wordt de views/index.hbs weergave getoond die u eerder hebt gemaakt in het bouwen van app-UI-onderdelen. isAuthenticated is een Booleaanse variabele die bepaalt wat u in de weergave ziet.

Aanmelden en afmelden

  1. Open in de code-editor routes/auth.js bestand en voeg vervolgens de volgende code toe:

    const express = require('express');
    const authController = require('../controller/authController');
    const router = express.Router();
    
    router.get('/signin', authController.signIn);
    router.get('/signout', authController.signOut);
    router.post('/redirect', authController.handleRedirect);
    
    module.exports = router;
    
  2. Open in de code-editor controller-/authController.js-bestand en voeg vervolgens de volgende code toe:

    const authProvider = require('../auth/AuthProvider');
    
    exports.signIn = async (req, res, next) => {
        return authProvider.login(req, res, next);
    };
    
    exports.handleRedirect = async (req, res, next) => {
        return authProvider.handleRedirect(req, res, next);
    }
    
    exports.signOut = async (req, res, next) => {
        return authProvider.logout(req, res, next);
    };
    
    
  3. Open in de code-editor auth/AuthProvider.js-bestand en voeg de volgende code toe:

    const msal = require('@azure/msal-node');
    const axios = require('axios');
    const { msalConfig, TENANT_SUBDOMAIN, REDIRECT_URI, POST_LOGOUT_REDIRECT_URI, GRAPH_ME_ENDPOINT} = require('../authConfig');
    
    class AuthProvider {
        config;
        cryptoProvider;
    
        constructor(config) {
            this.config = config;
            this.cryptoProvider = new msal.CryptoProvider();
        }
    
        getMsalInstance(msalConfig) {
            return new msal.ConfidentialClientApplication(msalConfig);
        }
    
        async login(req, res, next, options = {}) {
            // create a GUID for crsf
            req.session.csrfToken = this.cryptoProvider.createNewGuid();
    
            /**
             * The 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({
                    csrfToken: req.session.csrfToken,
                    redirectTo: '/',
                })
            );
    
            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: [],
            };
    
            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: [],
            };
    
            /**
             * 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.
             */
            if (!this.config.msalConfig.auth.authorityMetadata) {
                const authorityMetadata = await this.getAuthorityMetadata();
                this.config.msalConfig.auth.authorityMetadata = JSON.stringify(authorityMetadata);
            }
    
            const msalInstance = this.getMsalInstance(this.config.msalConfig);
    
            // trigger the first leg of auth code flow
            return this.redirectToAuthCodeUrl(
                req,
                res,
                next,
                authCodeUrlRequestParams,
                authCodeRequestParams,
                msalInstance
            );
        }
    
        async handleRedirect(req, res, next) {
            const authCodeRequest = {
                ...req.session.authCodeRequest,
                code: req.body.code, // authZ code
                codeVerifier: req.session.pkceCodes.verifier, // PKCE Code Verifier
            };
    
            try {
                const msalInstance = this.getMsalInstance(this.config.msalConfig);
                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.redirectTo);
            } catch (error) {
                next(error);
            }
        }
    
        async logout(req, res, next) {
            /**
             * Construct a logout URI and redirect the user to end the
             * session with Microsoft Entra ID. For more information, visit:
             * https://docs.microsoft.com/azure/active-directory/develop/v2-protocols-oidc#send-a-sign-out-request
             */
            //For external tenant
            //const logoutUri = `${this.config.msalConfig.auth.authority}${TENANT_SUBDOMAIN}.onmicrosoft.com/oauth2/v2.0/logout?post_logout_redirect_uri=${this.config.postLogoutRedirectUri}`;
    
            //For workforce tenant
            let logoutUri = `${this.config.msalConfig.auth.authority}/oauth2/v2.0/logout?post_logout_redirect_uri=${this.config.postLogoutRedirectUri}`;
            req.session.destroy(() => {
                res.redirect(logoutUri);
            });
        }
    
        /**
         * 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
         */
        async redirectToAuthCodeUrl(req, res, next, authCodeUrlRequestParams, authCodeRequestParams, msalInstance) {
            // 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,
                redirectUri: this.config.redirectUri,
                responseMode: 'form_post', // recommended for confidential clients
                codeChallenge: req.session.pkceCodes.challenge,
                codeChallengeMethod: req.session.pkceCodes.challengeMethod,
            };
    
            req.session.authCodeRequest = {
                ...authCodeRequestParams,
                redirectUri: this.config.redirectUri,
                code: '',
            };
    
            try {
                const authCodeUrlResponse = await msalInstance.getAuthCodeUrl(req.session.authCodeUrlRequest);
                res.redirect(authCodeUrlResponse);
            } catch (error) {
                next(error);
            }
        }
    
        /**
         * Retrieves oidc metadata from the openid endpoint
         * @returns
         */
        async getAuthorityMetadata() {
            // For external tenant
            //const endpoint = `${this.config.msalConfig.auth.authority}${TENANT_SUBDOMAIN}.onmicrosoft.com/v2.0/.well-known/openid-configuration`;
    
            // For workforce tenant
            const endpoint = `${this.config.msalConfig.auth.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: msalConfig,
        redirectUri: REDIRECT_URI,
        postLogoutRedirectUri: POST_LOGOUT_REDIRECT_URI,
    });
    
    module.exports = authProvider;
    
    

    De routes /signin, /signout en /redirect worden gedefinieerd in het routes-/auth.js-bestand, maar u implementeert de logica in de klasse auth/AuthProvider.js.

  • Methode login beheert de route /signin:

    • De aanmeldingsstroom wordt gestart door het eerste deel van de verificatiecodestroom te activeren.

    • Hiermee wordt een vertrouwelijke clienttoepassing geïnitialiseerd exemplaar met behulp van msal-configuratieobject, msalConfig, dat u eerder hebt gemaakt.

          const msalInstance = this.getMsalInstance(this.config.msalConfig);
      

      De methode getMsalInstance wordt gedefinieerd als:

          getMsalInstance(msalConfig) {
              return new msal.ConfidentialClientApplication(msalConfig);
          }
      
    • Het eerste deel van de verificatiecodestroom genereert een aanvraag-URL voor autorisatiecode en leidt vervolgens om naar die URL om de autorisatiecode te verkrijgen. Deze eerste etappe wordt geïmplementeerd in de methode redirectToAuthCodeUrl. U ziet hoe we MSALs getAuthCodeUrl methode gebruiken om autorisatiecode-URL te genereren:

      //...
      const authCodeUrlResponse = await msalInstance.getAuthCodeUrl(req.session.authCodeUrlRequest);
      //...
      

      Vervolgens wordt u omgeleid naar de URL van de autorisatiecode zelf.

      //...
      res.redirect(authCodeUrlResponse);
      //...
      
  • Methode handleRedirect beheert de route /redirect:

    • U stelt deze URL in als omleidings-URI voor de web-app in het Microsoft Entra-beheercentrum eerder in quickstart: Gebruikers aanmelden in een voorbeeldweb-app.

    • Met dit eindpunt wordt het tweede deel van de verificatiecodestroom geïmplementeerd. De autorisatiecode wordt gebruikt om een id-token aan te vragen met behulp van de acquireTokenByCode methode van MSAL.

      //...
      const tokenResponse = await msalInstance.acquireTokenByCode(authCodeRequest, req.body);
      //...
      
    • Nadat u een antwoord hebt ontvangen, kunt u een Express-sessie maken en de gewenste informatie hierin opslaan. U moet isAuthenticated opnemen en deze instellen op true:

      //...        
      req.session.idToken = tokenResponse.idToken;
      req.session.account = tokenResponse.account;
      req.session.isAuthenticated = true;
      //...
      
  • Methode logout beheert de route /signout:

    async logout(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
            */
        const logoutUri = `${this.config.msalConfig.auth.authority}${TENANT_SUBDOMAIN}.onmicrosoft.com/oauth2/v2.0/logout?post_logout_redirect_uri=${this.config.postLogoutRedirectUri}`;
    
        req.session.destroy(() => {
            res.redirect(logoutUri);
        });
    }
    
    • Er wordt een afmeldingsaanvraag gestart.

    • Wanneer u de gebruiker wilt afmelden bij de toepassing, is het niet voldoende om de sessie van de gebruiker te beëindigen. U moet de gebruiker omleiden naar de logoutUri. Anders kan de gebruiker zich mogelijk opnieuw aanmelden bij uw applicaties zonder hun inloggegevens opnieuw in te voeren. Als de naam van uw tenant is contoso, ziet de logoutUri er ongeveer uit als https://contoso.ciamlogin.com/contoso.onmicrosoft.com/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000.

Uitlog-URI en authority-metadata-eindpunt voor je app

De afmeldings-URI van de app, logoutUri, en het metagegevenseindpunt van de instantie, endpoint, zien er voor externe en werknemershuurders anders uit. Bouw ze zoals hieronder wordt weergegeven:

//Logout URI for workforce tenant
const logoutUri = `${this.config.msalConfig.auth.authority}/oauth2/v2.0/logout?post_logout_redirect_uri=${this.config.postLogoutRedirectUri}`;

//authority metadata endpoint for workforce tenant
const endpoint = `${this.config.msalConfig.auth.authority}/v2.0/.well-known/openid-configuration`;

Id-tokenclaims weergeven

Open in de code-editor routes/users.js bestand en voeg vervolgens de volgende code toe:

const express = require('express');
const router = express.Router();

// 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 });
    }
);        
module.exports = router;

Als de gebruiker is geverifieerd, geeft de /id route id-tokenclaims weer met behulp van de views/id.hbs weergave. U hebt deze weergave eerder toegevoegd in Bouw UI-componenten van de app.

Een specifieke id-tokenclaim extraheren, zoals voornaam:

const givenName = req.session.account.idTokenClaims.given_name

Uw web-app voltooien

  1. Open in de code-editor app.js bestand en voeg de code uit app.js toe.

  2. Open in de code-editor server.js bestand en voeg de code uit server.js toe.

  3. Open package.json bestand in de code-editor en werk de eigenschap scripts bij naar:

    "scripts": {
    "start": "node server.js"
    }
    

De Node/Express.js-web-app uitvoeren en testen

Op dit moment kunt u uw knooppuntweb-app testen.

  1. Gebruik de stappen in Een nieuwe gebruiker aanmaken om een testgebruiker aan te maken in de personeelsomgeving. Als u geen toegang tot de tenant hebt, vraagt u de tenantbeheerder om de gebruiker voor u te maken.

  2. Voer de volgende opdrachten uit vanuit de projectmap om de server te starten:

    npm start
    
  3. Open uw browser en ga vervolgens naar http://localhost:3000. De pagina ziet er ongeveer als volgt uit:

    Schermopname van aanmelden bij een knooppuntweb-app.

  4. Klik op Aanmelden om het aanmeldproces te starten. De eerste keer dat u zich aanmeldt, wordt u gevraagd uw toestemming te geven zodat de toepassing u kan aanmelden en toegang krijgt tot uw profiel, zoals wordt weergegeven in de volgende schermafbeelding:

    Schermopname van het scherm voor gebruikerstoestemming

Nadat u bent aangemeld, wordt u teruggeleid naar de startpagina van de toepassing.

Volgende stap