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


Включение проверки подлинности в собственном одностраничном приложении с помощью Azure Active Directory B2C

В этой статье показано, как добавить проверку подлинности Azure Active Directory B2C (Azure AD B2C) в собственное одностраничное приложение (SPA). Узнайте, как создать приложение SPA с использованием библиотеки проверки подлинности Майкрософт для JavaScript (MSAL.js).

Используйте эту статью вместе со статьей Настройка аутентификации в примере одностраничного приложения с помощью Azure Active Directory B2C, заменив пример приложения SPA собственным приложением SPA.

Обзор

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

MSAL.js — это предоставляемая корпорацией Майкрософт библиотека проверки подлинности, упрощающая добавление поддержки проверки подлинности и авторизации в одностраничные приложения.

Совет

Весь код MSAL.js выполняется на стороне клиента. Вы можете заменить код Node.js и Express на стороне сервера другими решениями, например сценариями .NET Core, Java и PHP.

Предварительные требования

Ознакомьтесь с предварительными требованиями и действиями по интеграции в статье Настройка аутентификации в примере одностраничного приложения с помощью Azure Active Directory B2C.

Шаг 1. Создание проекта приложения SPA

Вы можете использовать существующий проект приложения SPA или создать новый. Чтобы создать проект, выполните следующие действия.

  1. Откройте командную оболочку и создайте каталог (например, MyApp). Этот каталог будет содержать код приложения, а также файлы пользовательского интерфейса и конфигурации.

  2. Перейдите к созданному каталогу.

  3. Используйте команду npm init, чтобы создать файл package.json для приложения. Эта команда запрашивает сведения о приложении (например, имя и версию приложения, а также имя начальной точки входа, файл index.js). Выполните следующую команду и примите значения по умолчанию:

npm init

Шаг 2. Установка зависимостей

Чтобы установить пакет Express, в командной оболочке выполните следующую команду:

npm install express

Для поиска статических файлов приложения код на стороне сервера использует пакет Path.

Чтобы установить пакет Path, в командной оболочке выполните следующую команду:

npm install path

Шаг 3. Настройка веб-сервера

В папке myApp создайте файл index.ts, содержащий следующий код:

// Initialize express
const express = require('express');
const app = express();

// The port to listen to incoming HTTP requests
const port = 6420;

// Initialize path
const path = require('path');

// Set the front-end folder to serve public assets.
app.use(express.static('App'));

// Set up a route for the index.html
app.get('*', (req, res) => {
    res.sendFile(path.join(__dirname + '/index.html'));
});

// Start the server, and listen for HTTP requests
app.listen(port, () => {
  console.log(`Listening on http://localhost:${port}`);
});

Шаг 4. Создание пользовательского интерфейса SPA

Добавьте файл приложения index.html SPA. Этот файл реализует пользовательский интерфейс, созданный с помощью платформы Bootstrap, и импортирует файлы сценариев для настройки, проверки подлинности и вызовов веб-API.

Ресурсы, на которые ссылается файл index.html, подробно описаны в следующей таблице.

Ссылка Определение
Библиотека MSAL.js Путь CDN к библиотеке JavaScript для проверки подлинности MSAL.js.
Таблица стилей Bootstrap Бесплатная интерфейсная платформа для ускорения и упрощения разработки веб-приложений. Эта платформа включает в себя шаблоны проектов на основе HTML и CSS.
policies.js Содержит пользовательские политики и потоки пользователей Azure AD B2C.
authConfig.js Содержит параметры конфигурации проверки подлинности.
authRedirect.js Содержит логику проверки подлинности.
apiConfig.js Содержит области веб-API и расположение конечной точки API.
api.js Определяет метод, используемый для вызова API и обработки его ответа.
ui.js Управляет элементами пользовательского интерфейса.

Для отрисовки файла индекса SPA в папке myApp создайте файл с именемindex.html, который содержит следующий фрагмент КОДА HTML:

<!DOCTYPE html>
<html>
    <head>
        <title>My Azure AD B2C test app</title>
    </head>
    <body>
        <h2>My Azure AD B2C test app</h2>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous" />
        <button type="button" id="signIn" class="btn btn-secondary" onclick="signIn()">Sign-in</button>
        <button type="button" id="signOut" class="btn btn-success d-none" onclick="signOut()">Sign-out</button>
        <h5 id="welcome-div" class="card-header text-center d-none"></h5>
        <br />
        <!-- Content -->
        <div class="card">
            <div class="card-body text-center">
                <pre id="response" class="card-text"></pre>
                <button type="button" id="callApiButton" class="btn btn-primary d-none" onclick="passTokenToApi()">Call API</button>
            </div>
        </div>
        <script src="https://alcdn.msauth.net/browser/2.14.2/js/msal-browser.min.js" integrity="sha384-ggh+EF1aSqm+Y4yvv2n17KpurNcZTeYtUZUvhPziElsstmIEubyEB6AIVpKLuZgr" crossorigin="anonymous"></script>

        <!-- Importing app scripts (load order is important) -->
        <script type="text/javascript" src="./apiConfig.js"></script>
        <script type="text/javascript" src="./policies.js"></script>
        <script type="text/javascript" src="./authConfig.js"></script>
        <script type="text/javascript" src="./ui.js"></script>

        <!-- <script type="text/javascript" src="./authRedirect.js"></script>   -->
        <!-- uncomment the above line and comment the line below if you would like to use the redirect flow -->
        <script type="text/javascript" src="./authRedirect.js"></script>
        <script type="text/javascript" src="./api.js"></script>
    </body>
</html>

Шаг 5. Настройка библиотеки проверки подлинности

Настройте интеграцию библиотеки MSAL.js с Azure AD B2C. Библиотека MSAL.js использует общий объект конфигурации для подключения к конечным точкам проверки подлинности арендатора Azure AD B2C.

Чтобы настроить библиотеку проверки подлинности, выполните указанные ниже действия.

  1. В папке myApp создайте папку App.

  2. В папке App создайте файл с именем authConfig.js.

  3. Добавьте приведенный ниже код JavaScript в файл authConfig.js:

    const msalConfig = {
        auth: {
        clientId: "<Application-ID>", 
        authority: b2cPolicies.authorities.signUpSignIn.authority, 
        knownAuthorities: [b2cPolicies.authorityDomain], 
        redirectUri: "http://localhost:6420",
        },
        cache: {
        cacheLocation: "localStorage", .
        storeAuthStateInCookie: false, 
        }
    };
    
    const loginRequest = {
    scopes: ["openid", ...apiConfig.b2cScopes],
    };
    
    const tokenRequest = {
    scopes: [...apiConfig.b2cScopes],
    forceRefresh: false
    };
    
  4. Замените <Application-ID> идентификатором регистрации приложения. Дополнительные сведения см. в разделе Настройка аутентификации в примере одностраничного приложения с помощью Azure Active Directory B2C.

Совет

Дополнительные сведения о параметрах конфигурации объекта MSAL см. в статье Настройка параметров проверки подлинности в одностраничном приложении при помощи Azure AD B2C.

Шаг 6. Указание потоков пользователей Azure AD B2C

Создайте файл policies.js, который содержит информацию о среде Azure AD B2C. Библиотека MSAL.js использует эти сведения для создания запросов на проверку подлинности для Azure AD B2C.

Чтобы указать потоки пользователей Azure AD B2C, выполните следующие действия.

  1. В папке App создайте файл с именем policies.js.

  2. Добавьте следующий код в файл policies.js:

    const b2cPolicies = {
        names: {
            signUpSignIn: "B2C_1_SUSI",
            editProfile: "B2C_1_EditProfile"
        },
        authorities: {
            signUpSignIn: {
                authority: "https://contoso.b2clogin.com/contoso.onmicrosoft.com/Your-B2C-SignInOrSignUp-Policy-Id",
            },
            editProfile: {
                authority: "https://contoso.b2clogin.com/contoso.onmicrosoft.com/Your-B2C-EditProfile-Policy-Id"
            }
        },
        authorityDomain: "contoso.b2clogin.com"
    }
    
  3. Замените B2C_1_SUSI именем политики входа Azure AD B2C.

  4. Замените B2C_1_EditProfile именем политики изменения профилей Azure AD B2C.

  5. Замените все вхождения contoso именем своего арендатора Azure AD B2C.

Шаг 7. Использование MSAL для входа пользователя

На этом шаге реализуйте методы для инициализации потока входа, API получения маркера доступа и методов выхода.

Дополнительные сведения см. в статье Использование библиотеки проверки подлинности Майкрософт (MSAL) для входа пользователя .

Чтобы выполнить вход пользователя, сделайте следующее.

  1. В папке App создайте файл с именем authRedirect.js.

  2. Скопируйте и вставьте следующий код в файл authRedirect.json:

    // Create the main myMSALObj instance
    // configuration parameters are located at authConfig.js
    const myMSALObj = new msal.PublicClientApplication(msalConfig);
    
    let accountId = "";
    let idTokenObject = "";
    let accessToken = null;
    
    myMSALObj.handleRedirectPromise()
        .then(response => {
            if (response) {
                /**
                 * For the purpose of setting an active account for UI update, we want to consider only the auth response resulting
                 * from SUSI flow. "tfp" claim in the id token tells us the policy (NOTE: legacy policies may use "acr" instead of "tfp").
                 * To learn more about B2C tokens, visit https://learn.microsoft.com/azure/active-directory-b2c/tokens-overview
                 */
                if (response.idTokenClaims['tfp'].toUpperCase() === b2cPolicies.names.signUpSignIn.toUpperCase()) {
                    handleResponse(response);
                }
            }
        })
        .catch(error => {
            console.log(error);
        });
    
    
    function setAccount(account) {
        accountId = account.homeAccountId;
        idTokenObject = account.idTokenClaims;
        myClaims= JSON.stringify(idTokenObject);
        welcomeUser(myClaims);
    }
    
    function selectAccount() {
    
        /**
         * See here for more information on account retrieval: 
         * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md
         */
    
        const currentAccounts = myMSALObj.getAllAccounts();
    
        if (currentAccounts.length < 1) {
            return;
        } else if (currentAccounts.length > 1) {
    
            /**
             * Due to the way MSAL caches account objects, the auth response from initiating a user-flow
             * is cached as a new account, which results in more than one account in the cache. Here we make
             * sure we are selecting the account with homeAccountId that contains the sign-up/sign-in user-flow, 
             * as this is the default flow the user initially signed-in with.
             */
            const accounts = currentAccounts.filter(account =>
                account.homeAccountId.toUpperCase().includes(b2cPolicies.names.signUpSignIn.toUpperCase())
                &&
                account.idTokenClaims.iss.toUpperCase().includes(b2cPolicies.authorityDomain.toUpperCase())
                &&
                account.idTokenClaims.aud === msalConfig.auth.clientId 
                );
    
            if (accounts.length > 1) {
                // localAccountId identifies the entity for which the token asserts information.
                if (accounts.every(account => account.localAccountId === accounts[0].localAccountId)) {
                    // All accounts belong to the same user
                    setAccount(accounts[0]);
                } else {
                    // Multiple users detected. Logout all to be safe.
                    signOut();
                };
            } else if (accounts.length === 1) {
                setAccount(accounts[0]);
            }
    
        } else if (currentAccounts.length === 1) {
            setAccount(currentAccounts[0]);
        }
    }
    
    // in case of page refresh
    selectAccount();
    
    async function handleResponse(response) {
    
        if (response !== null) {
            setAccount(response.account);
        } else {
            selectAccount();
        }
    }
    
    function signIn() {
        myMSALObj.loginRedirect(loginRequest);
    }
    
    function signOut() {
        const logoutRequest = {
            postLogoutRedirectUri: msalConfig.auth.redirectUri,
        };
    
        myMSALObj.logoutRedirect(logoutRequest);
    }
    
    function getTokenRedirect(request) {
        request.account = myMSALObj.getAccountByHomeId(accountId); 
    
        return myMSALObj.acquireTokenSilent(request)
            .then((response) => {
                // In case the response from B2C server has an empty accessToken field
                // throw an error to initiate token acquisition
                if (!response.accessToken || response.accessToken === "") {
                    throw new msal.InteractionRequiredAuthError;
                } else {
                    console.log("access_token acquired at: " + new Date().toString());
                    accessToken = response.accessToken;
                    passTokenToApi();
                }
            }).catch(error => {
                console.log("Silent token acquisition fails. Acquiring token using popup. \n", error);
                if (error instanceof msal.InteractionRequiredAuthError) {
                    // fallback to interaction when silent call fails
                    return myMSALObj.acquireTokenRedirect(request);
                } else {
                    console.log(error);   
                }
        });
    }
    
    // Acquires and access token and then passes it to the API call
    function passTokenToApi() {
        if (!accessToken) {
            getTokenRedirect(tokenRequest);
        } else {
            try {
                callApi(apiConfig.webApi, accessToken);
            } catch(error) {
                console.log(error); 
            }
        }
    }
    
    function editProfile() {
    
    
        const editProfileRequest = b2cPolicies.authorities.editProfile;
        editProfileRequest.loginHint = myMSALObj.getAccountByHomeId(accountId).username;
    
        myMSALObj.loginRedirect(editProfileRequest);
    }
    

Шаг 8. Настройка расположения и области веб-API

Чтобы приложение SPA могло вызывать веб-API, укажите расположение конечной точки веб-API и области, используемые для авторизации доступа к веб-API.

Чтобы настроить расположение и области веб-API, выполните следующие действия.

  1. В папке App создайте файл с именем apiConfig.js.

  2. Скопируйте и вставьте следующий код в файл apiConfig.json:

    // The current application coordinates were pre-registered in a B2C tenant.
    const apiConfig = {
        b2cScopes: ["https://contoso.onmicrosoft.com/tasks/tasks.read"],
        webApi: "https://mydomain.azurewebsites.net/tasks"
    };
    
  3. Замените contoso именем своего клиента. Требуемое имя области можно найти, выполнив инструкции в статье Настройка областей.

  4. Замените значение webApi расположением конечной точки веб-API.

Шаг 9. Вызов веб-API

Определите HTTP-запрос к конечной точке API. Этот HTTP-запрос настраивается для передачи маркера доступа, полученного с помощью MSAL.js, в заголовок HTTP Authorization в запросе.

Следующий код определяет HTTP-запрос GET к конечной точке API, передавая маркер доступа в заголовке HTTP Authorization. Расположение API определяется ключом webApi в apiConfig.js.

Чтобы вызвать веб-API с помощью полученного маркера, выполните следующие действия.

  1. В папке App создайте файл с именем api.js.

  2. Добавьте следующий код в файл api.js:

    function callApi(endpoint, token) {
    
        const headers = new Headers();
        const bearer = `Bearer ${token}`;
    
        headers.append("Authorization", bearer);
    
        const options = {
            method: "GET",
            headers: headers
        };
    
        logMessage('Calling web API...');
    
        fetch(endpoint, options)
        .then(response => response.json())
        .then(response => {
    
            if (response) {
            logMessage('Web API responded: ' + response.name);
            }
    
            return response;
        }).catch(error => {
            console.error(error);
        });
    }
    

Шаг 10. Добавление ссылки на элементы пользовательского интерфейса

Приложение SPA использует JavaScript для управления элементами пользовательского интерфейса. Например, он отображает кнопки входа и выхода, а также отображает утверждения маркера идентификации пользователей на экране.

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

  1. В папке App создайте файл с именем ui.js.

  2. Добавьте следующий код в файл ui.js:

    // Select DOM elements to work with
    const signInButton = document.getElementById('signIn');
    const signOutButton = document.getElementById('signOut')
    const titleDiv = document.getElementById('title-div');
    const welcomeDiv = document.getElementById('welcome-div');
    const tableDiv = document.getElementById('table-div');
    const tableBody = document.getElementById('table-body-div');
    const editProfileButton = document.getElementById('editProfileButton');
    const callApiButton = document.getElementById('callApiButton');
    const response = document.getElementById("response");
    const label = document.getElementById('label');
    
    function welcomeUser(claims) {
        welcomeDiv.innerHTML = `Token claims: </br></br> ${claims}!`
    
        signInButton.classList.add('d-none');
        signOutButton.classList.remove('d-none');
        welcomeDiv.classList.remove('d-none');
        callApiButton.classList.remove('d-none');
    }
    
    function logMessage(s) {
        response.appendChild(document.createTextNode('\n' + s + '\n'));
    }
    

Шаг 11. Запуск приложения SPA

В командной оболочке выполните следующие команды:

npm install  
npm ./index.js
  1. Перейдите на сайт https://localhost:6420..
  2. Выберите Вход.
  3. Завершите процесс регистрации или входа в систему.

После успешного выполнения проверки подлинности проанализированный маркер для идентификатора отображается на экране. Чтобы вызвать конечную точку API, выберите Call API.

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