Dela via


Självstudie: Lägga till tillägg av inloggning i en Node/Express.js-webbapp med hjälp av Microsofts identitetsplattform

Gäller för: Grön cirkel med en vit bockmarkeringssymbol. Workforce-hyresgäster Grön cirkel med en vit bockmarkeringssymbol. externa hyresgäster (läs mer)

I den här självstudien lägger du till inloggnings- och utloggningslogik i din Node/Express-webbapp. Med den här koden kan du logga in användare i din kundinriktade app antingen via en extern klientorganisation eller som anställda i en arbetsstyrke-klientorganisation.

Den här handledningen är del 2 av en handledningsserie i tre delar.

I den här handledningen kommer du att:

  • Lägga till inloggnings- och utloggningslogik
  • Visa ID-tokensanspråk
  • Kör appen och testa inloggnings- och utloggningsupplevelsen.

Förutsättningar

Skapa MSAL-konfigurationsobjekt

Öppna authConfig.js fil i kodredigeraren och lägg sedan till följande kod:

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
};

Objektet msalConfig innehåller en uppsättning konfigurationsalternativ som du använder för att anpassa beteendet för dina autentiseringsflöden.

Ersätt i filen authConfig.js:

  • Enter_the_Application_Id_Here med program-ID:t (klient) för den app som du registrerade tidigare.

  • Enter_the_Tenant_Subdomain_Here och ersätt den med underdomänen i den externa katalogen (hyresgäst). Om din primära klientdomän till exempel är contoso.onmicrosoft.comanvänder du contoso. Om du inte har ditt klientnamn, lär dig så här att läsa klientinformationen. Det här värdet krävs endast för externa hyresgäster.

  • Enter_the_Client_Secret_Here med det hemliga värde för appen som du kopierade tidigare.

  • Enter_the_Graph_Endpoint_Here med den Microsoft Graph API-molninstans som din app kommer att anropa. Använd värdet https://graph.microsoft.com/ (inkludera avslutande snedstreck)

Om du använder filen .env för att lagra konfigurationsinformationen:

  1. Öppna .env fil i kodredigeraren och lägg sedan till följande kod.

        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. Ersätt platshållaren:

    1. Enter_the_Application_Id_Here, Enter_the_Tenant_Subdomain_Here och Enter_the_Client_Secret_Here som beskrevs tidigare.
    2. Enter_the_Cloud_Instance_Id_Here med Azure-molninstansen där ditt program är registrerat. Använd https://login.microsoftonline.com/ som dess värde (inkludera det avslutande snedstrecket). Detta värde krävs endast för arbetskraftshyresgästen.
    3. Enter_the_Tenant_ID_here med arbetsstyrkans klient-ID eller primära domän, såsom aaaabbbb-0000-cccc-1111-dddd2222eeee eller contoso.microsoft.com. Det här värdet krävs endast för arbetskraftsanvändare.

Du exporterar msalConfig, REDIRECT_URI, TENANT_SUBDOMAIN, GRAPH_ME_ENDPOINT och POST_LOGOUT_REDIRECT_URI variabler i authConfig.js-filen för att göra dem tillgängliga i andra filer.

Auktoritets-URL för din app

Ansökningsmyndigheterna för externa hyresgäster och medarbetarhyresgäster ser annorlunda ut. Skapa dem enligt nedan:

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

Använda anpassad URL-domän (valfritt)

Anpassade URL-domäner stöds inte i personalklienter.

Lägga till expressvägar

Expressrutterna tillhandahåller de slutpunkter som gör att vi kan köra operationer som inloggning, utloggning och visa ID-tokenpåståenden.

Startpunkt för app

I kodredigeraren öppnar du vägar/index.js fil och lägger sedan till följande kod:

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;

/-rutten är ingångspunkten till applikationen. Den visar vyer/index.hbs vy som du skapade tidigare i Bygg appens UI-komponenter. isAuthenticated är en boolesk variabel som avgör vad du ser i vyn.

Logga in och logga ut

  1. I kodredigeraren öppnar du vägar/auth.js fil och lägger sedan till följande kod:

    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. I kodredigeraren öppnar du kontrollant/authController.js fil och lägger sedan till följande kod:

    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. I kodredigeraren öppnar du autentisering/AuthProvider.js fil och lägger sedan till följande kod:

    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;
    
    

    Vägarna /signin, /signout och /redirect definieras i filen routes/auth.js, men du implementerar deras logik i klassen auth/AuthProvider.js.

  • Metoden login hanterar /signin väg:

    • Det initierar inloggningsflödet genom att utlösa den första delen av autentiseringskodflödet.

    • Den initierar ett konfidentiellt klientprogram instans med hjälp av MSAL-konfigurationsobjektet msalConfig, som du skapade tidigare.

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

      Metoden getMsalInstance definieras som:

          getMsalInstance(msalConfig) {
              return new msal.ConfidentialClientApplication(msalConfig);
          }
      
    • Den första delen av autentiseringskodflödet genererar en URL för begäran om auktoriseringskod och omdirigeras sedan till den URL:en för att hämta auktoriseringskoden. Den här första steget implementeras i metoden redirectToAuthCodeUrl. Observera hur vi använder MSALs getAuthCodeUrl-metod för att generera auktoriseringskod-URL:

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

      Sedan omdirigerar vi till själva auktoriseringskodens URL.

      //...
      res.redirect(authCodeUrlResponse);
      //...
      
  • Metoden handleRedirect hanterar /redirect väg:

    • Du anger den här URL:en som omdirigerings-URI för webbappen i administrationscentret för Microsoft Entra tidigare i Snabbstart: Logga in användare i en exempelwebbapp.

    • Den här slutpunkten implementerar det andra steget i användningen av autentiseringskodflödet. Den använder auktoriseringskoden för att med hjälp av metoden acquireTokenByCode i MSAL:s begära en ID-token.

      //...
      const tokenResponse = await msalInstance.acquireTokenByCode(authCodeRequest, req.body);
      //...
      
    • När du har fått ett svar kan du skapa en Express-session och lagra den information du vill ha i den. Du måste inkludera isAuthenticated och ställa in den på true:

      //...        
      req.session.idToken = tokenResponse.idToken;
      req.session.account = tokenResponse.account;
      req.session.isAuthenticated = true;
      //...
      
  • Metoden logout hanterar /signout väg:

    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);
        });
    }
    
    • Den initierar utloggningsbegäran.

    • När du vill logga ut användaren från programmet räcker det inte att avsluta användarens session. Du måste omdirigera användaren till logoutUri. Annars kanske användaren kan autentisera till dina program igen utan att ange sina autentiseringsuppgifter igen. Om namnet på din klientorganisation är contoso, kommer logoutUri att se ut ungefär som https://contoso.ciamlogin.com/contoso.onmicrosoft.com/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000.

Slutpunkt för utloggnings-URI och auktoritetsmetadata för din app

Appens utloggnings-URI, logoutUri och myndighetens metadataslutpunkt, endpoint för externa och interna tenanter ser annorlunda ut. Skapa dem enligt nedan:

//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`;

Visa ID-tokenanspråk

I kodredigeraren öppnar du vägar/users.js fil och lägger sedan till följande kod:

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;

Om användaren är autentiserad visar /id-rutten ID-tokenanspråk med hjälp av -vyer/id.hbs--vyn. Du har lagt till den här vyn tidigare i Build app UI components.

För att extrahera ett specifikt ID-tokenpåstående, till exempel förnamn:

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

Slutför din webbapp

  1. Öppna app.js fil i kodredigeraren och lägg sedan till koden från app.js till den.

  2. Öppna server.js fil i kodredigeraren och lägg sedan till koden från server.js till den.

  3. Öppna package.json fil i kodredigeraren och uppdatera sedan egenskapen scripts till:

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

Köra och testa webbappen Node/Express.js

Nu kan du testa Node-webbappen.

  1. Använd stegen i Skapa en ny användare för att skapa en testanvändare i arbetsdomänen. Om du inte har åtkomst till klientorganisationen ber du klientadministratören att skapa användaren åt dig.

  2. Starta servern genom att köra följande kommandon från projektkatalogen:

    npm start
    
  3. Öppna webbläsaren och gå sedan till http://localhost:3000. Du bör se sidan som liknar följande skärmbild:

    Skärmbild av inloggning i en nodwebbapp.

  4. Välj Logga in för att starta inloggningsprocessen. Första gången du loggar in uppmanas du att ge ditt medgivande så att programmet kan logga in och komma åt din profil enligt följande skärmbild:

    Skärmbild som visar skärmen med användarmedgivande

När du har loggat in omdirigeras du tillbaka till programmets startsida.

Nästa steg