Поделиться через


Руководство: Добавление возможности авторизации в веб-приложение Node/Express.js с помощью платформы идентификации Microsoft

применяется к: зеленый круг с символом белой флажки. арендаторы рабочей силы зеленый круг с символом белой галочки. внешние клиенты (подробнее)

В этом руководстве описано, как добавить логику входа и выхода в веб-приложение Node/Express. Этот код позволяет пользователям входить в ваше клиентское приложение через внешний арендатор или сотрудникам в арендаторе рабочей силы.

Это часть 2 из серии учебников, состоящей из 3 частей.

Изучив это руководство, вы:

  • Добавление логики входа и выхода
  • Просмотр утверждений идентификационного токена
  • Запустите приложение и проверьте возможности входа и выхода.

Необходимые условия

Создание объекта конфигурации MSAL

В редакторе кода откройте файл authConfig.js, а затем добавьте следующий код:

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

Объект msalConfig содержит набор параметров конфигурации, используемых для настройки поведения потоков проверки подлинности.

В файле authConfig.js замените:

  • Enter_the_Application_Id_Here с идентификатором приложения (клиента), зарегистрированного ранее.

  • Enter_the_Tenant_Subdomain_Here а затем замените его поддоменом внешней директории (арендатора). Например, если основной домен клиента contoso.onmicrosoft.com, используйте contoso. Если у вас нет имени арендатора, узнайте, как просматривать сведения о арендаторе. Это значение требуется только для внешнего арендатора.

  • Используйте значение секрета приложения Enter_the_Client_Secret_Here, скопированное ранее.

  • Enter_the_Graph_Endpoint_Here с облачным экземпляром Microsoft Graph API, который ваше приложение будет вызывать. Используйте значение https://graph.microsoft.com/ (не забудьте включить конечную косую черту)

Если вы используете файл .env для хранения сведений о конфигурации:

  1. В редакторе кода откройте файл .env, а затем добавьте следующий код.

        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. Замените заполнитель:

    1. Enter_the_Application_Id_Here, Enter_the_Tenant_Subdomain_Here и Enter_the_Client_Secret_Here, как описано ранее.
    2. Enter_the_Cloud_Instance_Id_Here в облачном экземпляре Azure, где зарегистрировано ваше приложение. Используйте https://login.microsoftonline.com/ в качестве значения (включите заключительную косую черту). Это значение необходимо только для арендатора рабочей силы.
    3. Enter_the_Tenant_ID_here с идентификатором арендатора рабочей силы или основным доменом, например, aaaabbbb-0000-cccc-1111-dddd2222eeee, или contoso.microsoft.com. Это значение необходимо только для арендатора рабочей силы.

Вы экспортируете msalConfig, REDIRECT_URI, TENANT_SUBDOMAIN, GRAPH_ME_ENDPOINT и POST_LOGOUT_REDIRECT_URI переменные в файле authConfig.js, чтобы сделать их доступными в других файлах.

URL-адрес авторизации для вашего приложения

Полномочия приложений для внешних и штатных пользователей различаются. Создайте их, как показано ниже:

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

Использование личного домена URL-адреса (необязательно)

В арендаторах рабочей среды не поддерживаются настраиваемые домены URL.

Добавление экспресс-маршрутов

Маршруты Express предоставляют конечные точки, позволяющие выполнять такие операции, как вход, выход и просмотр утверждений токена идентификатора.

Точка входа приложения

В редакторе кода откройте файл маршрутов /index.js, а затем добавьте следующий код:

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;

Маршрут / — это точка входа в приложение. Он отображает вид views/index.hbs, который вы создали ранее в разделе Создание компонентов пользовательского интерфейса приложения. isAuthenticated — это логическая переменная, которая определяет, что вы видите в интерфейсе.

Вход и выход

  1. В редакторе кода откройте файл маршрутов /auth.js, а затем добавьте следующий код:

    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. В редакторе кода откройте файл контроллера илиauthController.js, а затем добавьте следующий код:

    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. В редакторе кода откройте файл проверки подлинности илиAuthProvider.js, а затем добавьте следующий код:

    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;
    
    

    Маршруты /signin, /signout и /redirect определены в файле маршрутов/auth.js, а их логика реализуется в классе auth/AuthProvider.js.

  • Метод login обрабатывает маршрут /signin:

    • Он инициирует поток входа, активируя первый этап потока кода проверки подлинности.

    • Он инициализирует экземпляр конфиденциального клиентского приложения с помощью объекта конфигурации MSAL, msalConfig, созданного ранее.

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

      Метод getMsalInstance определяется следующим образом:

          getMsalInstance(msalConfig) {
              return new msal.ConfidentialClientApplication(msalConfig);
          }
      
    • Первый этап потока кода проверки подлинности создает URL-адрес запроса кода авторизации, а затем перенаправляется на этот URL-адрес, чтобы получить код авторизации. Этот первый этап реализуется в методе redirectToAuthCodeUrl. Обратите внимание, что мы используем MSALs метод getAuthCodeUrl для создания URL-адреса кода авторизации:

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

      Затем мы перенаправляем на URL-адрес кода авторизации.

      //...
      res.redirect(authCodeUrlResponse);
      //...
      
  • Метод handleRedirect обрабатывает маршрут /redirect:

    • Этот URL-адрес был установлен как URI перенаправления для веб-приложения в Центре администрирования Microsoft Entra ранее в разделе "Краткое руководство: Вход пользователей в пример веб-приложение".

    • Эта конечная точка реализует второй этап потока кода проверки подлинности. Он использует код авторизации для запроса ID-токена с помощью метода MSAL acquireTokenByCode.

      //...
      const tokenResponse = await msalInstance.acquireTokenByCode(authCodeRequest, req.body);
      //...
      
    • Получив ответ, вы можете создать сеанс Express и сохранить в нем все необходимые сведения. Необходимо включить isAuthenticated и установить для него значение true:

      //...        
      req.session.idToken = tokenResponse.idToken;
      req.session.account = tokenResponse.account;
      req.session.isAuthenticated = true;
      //...
      
  • Метод logout обрабатывает маршрут /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);
        });
    }
    
    • Он инициирует запрос на выход.

    • Если вы хотите выйти пользователя из приложения, недостаточно просто завершить его сеанс. Необходимо перенаправить пользователя на logoutUri. В противном случае пользователь может повторно выполнить проверку подлинности в приложениях без повторного ввода учетных данных. Если имя вашего арендатора contoso, то logoutUri выглядит примерно так же, как https://contoso.ciamlogin.com/contoso.onmicrosoft.com/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000.

URI разлогина и конечная точка метаданных авторизации для вашего приложения

URI выхода приложения, logoutUri и конечная точка метаданных полномочий, endpoint для внешних и рабочих арендаторов выглядят иначе. Создайте их, как показано ниже:

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

Просмотр утверждений идентификационного токена

В редакторе кода откройте файл маршрутов /users.js, а затем добавьте следующий код:

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;

Если пользователь прошел проверку подлинности, маршрут /id отображает утверждения маркера идентификатора с помощью представления /views/id.hbs. Вы добавили это представление ранее в Сборка компонентов пользовательского интерфейса приложения.

Чтобы извлечь определенное утверждение маркера идентификатора, например заданное имя:

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

Завершение работы веб-приложения

  1. В редакторе кода откройте файл app.js, а затем добавьте в него код из app.js.

  2. В редакторе кода откройте файл server.js, а затем добавьте в него код из server.js.

  3. В редакторе кода откройте файл package.json, а затем обновите свойство scripts следующим образом:

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

Запуск и проверка веб-приложения Node/Express.js

На этом этапе вы можете протестировать веб-приложение узла.

  1. Выполните действия, описанные в Создание нового пользователя для создания тестового пользователя в клиенте рабочей силы. Если у вас нет доступа к клиенту, попросите администратора клиента создать пользователя.

  2. Чтобы запустить сервер, выполните следующие команды из каталога проекта:

    npm start
    
  3. Откройте браузер, а затем перейдите к http://localhost:3000. Вы увидите страницу, аналогичную следующему снимку экрана:

    снимок экрана входа в веб-приложение узла.

  4. Выберите Войдите в систему, чтобы запустить процесс входа. При первом входе вам будет предложено предоставить согласие на вход и доступ к вашему профилю, как показано на следующем снимке экрана:

    снимок экрана, на котором отображается экран согласия пользователя

После успешного входа вы будете перенаправлены обратно на домашнюю страницу приложения.

Следующий шаг

Руководство: вызов API Microsoft Graph из вашего веб-приложения на Node/Express.js.