Sdílet prostřednictvím


Kurz: Přidání přihlášení do webové aplikace Node/Express.js pomocí platformy Microsoft Identity Platform

Platí pro: Zelený kruh se symbolem bílé značky zaškrtnutí, který označuje následující obsah platí pro tenanty pracovních sil. Tenanti pracovních sil zelený kruh se symbolem bílé značky zaškrtnutí, který označuje následující obsah platí pro externí tenanty. Externí tenanti (další informace)

V tomto kurzu přidáte logiku přihlášení a odhlášení do webové aplikace Node/Express. Tento kód umožňuje přihlašovat uživatele do vaší aplikace pro zákazníky, a to buď v externím tenantovi, nebo v pracovním tenantovi zaměstnanců.

Tento kurz je 2. částí třídílné série kurzů.

V tomto kurzu se naučíte:

  • Přidání logiky přihlašování a odhlášení
  • Zobrazení deklarací ID tokenu
  • Spusťte aplikaci a otestujte prostředí přihlašování a odhlášení.

Požadavky

Vytvoření objektu konfigurace MSAL

V editoru kódu otevřete souborauthConfig.js a přidejte následující kód:

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

Objekt msalConfig obsahuje sadu možností konfigurace, které používáte k přizpůsobení chování toků ověřování.

V souboruauthConfig.js nahraďte:

  • Enter_the_Application_Id_Here s ID aplikace (klienta), kterou jste zaregistrovali dříve.

  • Enter_the_Tenant_Subdomain_Here a nahraďte ji subdoménou externího adresáře (tenanta). Pokud je například primární doména vašeho tenanta contoso.onmicrosoft.com, použijte contoso. Jestliže nemáte jméno tenanta, zjistěte, jak si přečíst informace o svém tenantovi. Tato hodnota se vyžaduje jenom pro externího tenanta.

  • Enter_the_Client_Secret_Here s hodnotou tajného klíče aplikace, kterou jste dříve zkopírovali.

  • Enter_the_Graph_Endpoint_Here s cloudovou instancí rozhraní Microsoft Graph API, kterou bude vaše aplikace volat. Použijte hodnotu https://graph.microsoft.com/ (včetně koncového lomítka).

Pokud k uložení konfiguračních informací použijete soubor .env :

  1. V editoru kódu otevřete soubor .env a přidejte následující kód.

        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. Zástupný symbol nahraďte:

    1. Enter_the_Application_Id_Here Enter_the_Tenant_Subdomain_Here a Enter_the_Client_Secret_Here jak bylo vysvětleno dříve.
    2. Enter_the_Cloud_Instance_Id_Here s instancí cloudu Azure, ve které je vaše aplikace zaregistrovaná. Použijte https://login.microsoftonline.com/ jako hodnotu (včetně koncového lomítka). Tato hodnota se vyžaduje jenom pro tenanta pracovních sil.
    3. Enter_the_Tenant_ID_here s ID tenanta pracovní síly nebo primární doménou, jako například aaaabbbb-0000-cccc-1111-dddd2222eeee nebo contoso.microsoft.com. Tato hodnota se vyžaduje jenom pro tenanta pracovních sil.

Exportujete msalConfig, REDIRECT_URI, TENANT_SUBDOMAINGRAPH_ME_ENDPOINT a POST_LOGOUT_REDIRECT_URI proměnné v souboruauthConfig.js, aby byly přístupné v jiných souborech.

Adresa URL autority pro vaši aplikaci

Orgány pro aplikace pro externí nájemce a nájemce z řad pracovních sil se liší. Sestavte je, jak je znázorněno níže:

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

Použití vlastní domény URL (volitelné)

Vlastní domény URL nejsou podporovány v tenantech pracovních sil.

Přidání expresních tras

Trasy Express poskytují koncové body, které nám umožňují provádět operace, jako je přihlášení, odhlášení a zobrazení deklarací identity tokenu ID.

Vstupní bod aplikace

V editoru kódu otevřete trasy/index.js soubor a přidejte následující kód:

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;

Trasa / je vstupním bodem aplikace. Zobrazí pohled views/index.hbs, který jste vytvořili dříve v části Sestavení součástí uživatelského rozhraní aplikace. isAuthenticated je logická proměnná, která určuje, co vidíte v zobrazení.

Přihlášení a odhlášení

  1. V editoru kódu otevřete trasy/auth.js soubor a přidejte následující kód:

    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. V editoru kódu otevřete soubor kontroleru neboauthController.js a přidejte následující kód:

    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. V editoru kódu otevřete soubor ověřování neboAuthProvider.js a přidejte následující kód:

    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;
    
    

    Trasy /signin, /signout a /redirect jsou definovány v souboru routes/auth.js, ale jejich logiku implementujete ve třídě auth/AuthProvider.js.

  • Metoda login zpracovává trasu /signin :

    • Spustí tok přihlášení tím, že aktivuje první část toku ověřovacího kódu.

    • Inicializuje důvěrnou instanci klientské aplikace pomocí objektu konfigurace MSAL , msalConfigkterý jste vytvořili dříve.

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

      Metoda getMsalInstance je definována takto:

          getMsalInstance(msalConfig) {
              return new msal.ConfidentialClientApplication(msalConfig);
          }
      
    • První část toku ověřovacího kódu vygeneruje adresu URL požadavku na autorizační kód a pak přesměruje na danou adresu URL a získá autorizační kód. Tato první fáze je implementována v metodě redirectToAuthCodeUrl. Všimněte si, že k vygenerování adresy URL autorizačního kódu používáme metodu MSALs getAuthCodeUrl :

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

      Pak přesměrujeme na samotnou adresu URL autorizačního kódu.

      //...
      res.redirect(authCodeUrlResponse);
      //...
      
  • Metoda handleRedirect zpracovává trasu /redirect :

    • Tuto adresu URL jste dříve nastavili jako identifikátor URI přesměrování pro webovou aplikaci v Centru pro správu Microsoft Entra v Rychlý start: Přihlášení uživatelů v ukázkové webové aplikaci.

    • Tento koncový bod implementuje druhou fázi použití toku ověřovacího kódu. Autorizační kód používá k vyžádání tokenu ID pomocí metody acquireTokenByCode knihovny MSAL.

      //...
      const tokenResponse = await msalInstance.acquireTokenByCode(authCodeRequest, req.body);
      //...
      
    • Jakmile obdržíte odpověď, můžete vytvořit relaci Expressu a uložit do ní libovolné informace. Musíte zahrnout isAuthenticated a nastavit ho na true:

      //...        
      req.session.idToken = tokenResponse.idToken;
      req.session.account = tokenResponse.account;
      req.session.isAuthenticated = true;
      //...
      
  • Metoda logout zpracovává trasu /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);
        });
    }
    
    • Zahájí žádost o odhlášení.

    • Pokud chcete uživatele odhlásit z aplikace, nestačí ukončit relaci uživatele. Uživatele musíte přesměrovat na logoutUri. V opačném případě může být uživatel schopen znovu provést ověření vašich aplikací bez opětovného zadání přihlašovacích údajů. Pokud je název vašeho tenanta contoso, vypadá logoutUri podobně jako https://contoso.ciamlogin.com/contoso.onmicrosoft.com/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000.

Výstupní URI a koncový bod metadat autority pro vaši aplikaci

Identifikátor URI logoutUri odhlášení aplikace a koncový bod endpoint metadat autority pro externí a podnikové tenanty vypadají jinak. Sestavte je, jak je znázorněno níže:

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

Zobrazení deklarací ID tokenu

V editoru kódu otevřete trasy/users.js soubor a přidejte následující kód:

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;

Pokud je uživatel ověřený, /id cesta zobrazí deklarace ID tokenu pomocí zobrazení views/id.hbs. Toto zobrazení jste přidali dříve v vytváření komponent uživatelského rozhraní aplikace.

Pokud chcete extrahovat konkrétní deklaraci identity tokenu ID, například název:

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

Dokončení webové aplikace

  1. V editoru kódu otevřete souborapp.js a pak do něj přidejte kód z app.js .

  2. V editoru kódu otevřete souborserver.js a pak do něj přidejte kód z server.js .

  3. V editoru kódu otevřete souborpackage.json a aktualizujte scripts vlastnost na:

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

Spuštění a otestování webové aplikace Node/Express.js

V tuto chvíli můžete webovou aplikaci node otestovat.

  1. Pomocí kroků v části Vytvoření nového uživatele vytvořte testovacího uživatele v tenantovi pracovních sil. Pokud nemáte přístup k tenantovi, požádejte správce tenanta, aby za vás vytvořil uživatele.

  2. Pokud chcete spustit server, spusťte v adresáři projektu následující příkazy:

    npm start
    
  3. Otevřete prohlížeč a přejděte na http://localhost:3000. Měla by se zobrazit stránka podobná následujícímu snímku obrazovky:

    Snímek obrazovky s přihlášením k webové aplikaci node

  4. Výběrem možnosti Přihlásit se spusťte proces přihlášení. Při prvním přihlášení se zobrazí výzva k zadání souhlasu, abyste aplikaci povolili přihlášení a přístup k vašemu profilu, jak je znázorněno na následujícím snímku obrazovky:

    Snímek obrazovky zobrazující souhlas uživatele

Po úspěšném přihlášení budete přesměrováni zpět na domovskou stránku aplikace.

Další krok