Włączanie uwierzytelniania we własnej aplikacji jednostronicowej przy użyciu usługi Azure AD B2C

W tym artykule przedstawiono sposób dodawania uwierzytelniania usługi Azure Active Directory B2C (Azure AD B2C) do własnej aplikacji jednostronicowej (SPA). Dowiedz się, jak utworzyć aplikację SPA przy użyciu biblioteki Microsoft Authentication Library for JavaScript (MSAL.js).

Użyj tego artykułu z tematem Konfigurowanie uwierzytelniania w przykładowej aplikacji SPA, zastępując przykładową aplikację SPA własną aplikacją SPA.

Omówienie

W tym artykule użyto Node.js i platformy Express do utworzenia podstawowej aplikacji internetowej Node.js. Platforma Express to minimalna i elastyczna platforma aplikacji internetowej Node.js, która udostępnia zestaw funkcji dla aplikacji internetowych i mobilnych.

Biblioteka uwierzytelniania MSAL.js to biblioteka udostępniana przez firmę Microsoft, która upraszcza dodawanie obsługi uwierzytelniania i autoryzacji do aplikacji SPA.

Porada

Cały kod MSAL.js jest uruchamiany po stronie klienta. Możesz zastąpić kod po stronie Node.js i serwera Express innymi rozwiązaniami, takimi jak .NET Core, Java i Hypertext Preprocessor (PHP) w językach skryptów.

Wymagania wstępne

Aby zapoznać się z wymaganiami wstępnymi i instrukcjami dotyczącymi integracji, zobacz Konfigurowanie uwierzytelniania w przykładowej aplikacji SPA.

Krok 1. Tworzenie projektu aplikacji SPA

Możesz użyć istniejącego projektu aplikacji SPA lub utworzyć nowy. Aby utworzyć nowy projekt, wykonaj następujące czynności:

  1. Otwórz powłokę poleceń i utwórz nowy katalog (na przykład myApp). Ten katalog będzie zawierać kod aplikacji, interfejs użytkownika i pliki konfiguracji.

  2. Wprowadź utworzony katalog.

  3. Użyj polecenia , npm init aby utworzyć package.json plik dla aplikacji. To polecenie wyświetla informacje o aplikacji (na przykład nazwę i wersję aplikacji oraz nazwę początkowego punktu wejścia, plik index.js ). Uruchom następujące polecenie i zaakceptuj wartości domyślne:

npm init

Krok 2. Instalowanie zależności

Aby zainstalować pakiet Express, w powłoce poleceń uruchom następujące polecenie:

npm install express

Aby zlokalizować pliki statyczne aplikacji, kod po stronie serwera używa pakietu Path .

Aby zainstalować pakiet Path, w powłoce poleceń uruchom następujące polecenie:

npm install path

Krok 3. Konfigurowanie serwera internetowego

W folderze myApp utwórz plik o nazwie index.js, który zawiera następujący kod:

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

Krok 4. Tworzenie interfejsu użytkownika SPA

Dodaj plik aplikacji index.html SPA. Ten plik implementuje interfejs użytkownika utworzony za pomocą platformy Bootstrap i importuje pliki skryptów do wywołań konfiguracji, uwierzytelniania i internetowego interfejsu API.

Zasoby, do których odwołuje się plik index.html , zostały szczegółowo opisane w poniższej tabeli:

Odwołanie Definicja
biblioteka MSAL.js MSAL.js uwierzytelnianie ścieżki cdN biblioteki JavaScript.
Arkusz stylów bootstrap Bezpłatna platforma frontonu umożliwiająca szybsze i łatwiejsze tworzenie aplikacji internetowych. Platforma zawiera szablony projektowe oparte na języku HTML i CSS.
policies.js Zawiera zasady niestandardowe Azure AD B2C i przepływy użytkowników.
authConfig.js Zawiera parametry konfiguracji uwierzytelniania.
authRedirect.js Zawiera logikę uwierzytelniania.
apiConfig.js Zawiera zakresy internetowego interfejsu API i lokalizację punktu końcowego interfejsu API.
api.js Definiuje metodę służącą do wywoływania interfejsu API i obsługi jego odpowiedzi.
ui.js Steruje elementami interfejsu użytkownika.

Aby renderować plik indeksu SPA, w folderze myApp utwórz plik o nazwie index.html, który zawiera następujący fragment kodu 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>

Krok 5. Konfigurowanie biblioteki uwierzytelniania

Skonfiguruj sposób integracji biblioteki MSAL.js z usługą Azure AD B2C. Biblioteka MSAL.js używa wspólnego obiektu konfiguracji do łączenia się z punktami końcowymi uwierzytelniania dzierżawy usługi Azure AD B2C.

Aby skonfigurować bibliotekę uwierzytelniania, wykonaj następujące czynności:

  1. W folderze myApp utwórz nowy folder o nazwie Aplikacja.

  2. W folderze App utwórz nowy plik o nazwie authConfig.js.

  3. Dodaj następujący kod JavaScript do pliku 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. Zastąp ciąg <Application-ID> identyfikatorem aplikacji rejestracji aplikacji. Aby uzyskać więcej informacji, zobacz Konfigurowanie uwierzytelniania w przykładowej aplikacji SPA.

Porada

Aby uzyskać więcej opcji konfiguracji obiektów MSAL, zobacz artykuł Opcje uwierzytelniania .

Krok 6. Określanie przepływów użytkownika Azure AD B2C

Utwórz plik policies.js, który zawiera informacje o środowisku usługi Azure AD B2C. Biblioteka MSAL.js używa tych informacji do tworzenia żądań uwierzytelniania w celu Azure AD B2C.

Aby określić przepływy użytkownika usługi Azure AD B2C, wykonaj następujące czynności:

  1. W folderze App utwórz nowy plik o nazwie policies.js.

  2. Dodaj następujący kod do pliku 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. Zastąp B2C_1_SUSI ciąg nazwą zasad logowania Azure AD B2C.

  4. Zastąp B2C_1_EditProfile element nazwą zasad edytowania profilu Azure AD B2C.

  5. Zastąp wszystkie wystąpienia contoso nazwą dzierżawy usługi Azure AD B2C.

Krok 7. Logowanie użytkownika przy użyciu biblioteki MSAL

W tym kroku zaimplementuj metody inicjowania przepływu logowania, uzyskiwania tokenu dostępu interfejsu API i metod wylogowania.

Aby uzyskać więcej informacji, zobacz artykuł Używanie biblioteki Microsoft Authentication Library (MSAL) do logowania się do użytkownika .

Aby zalogować się do użytkownika, wykonaj następujące czynności:

  1. W folderze App utwórz nowy plik o nazwie authRedirect.js.

  2. W authRedirect.jsskopiuj i wklej następujący kod:

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

Krok 8. Konfigurowanie lokalizacji i zakresu internetowego interfejsu API

Aby umożliwić aplikacji SPA wywoływanie internetowego interfejsu API, podaj lokalizację internetowego punktu końcowego interfejsu API i zakresy używane do autoryzowania dostępu do internetowego interfejsu API.

Aby skonfigurować lokalizację i zakresy internetowego interfejsu API, wykonaj następujące czynności:

  1. W folderze App utwórz nowy plik o nazwie apiConfig.js.

  2. W apiConfig.jsskopiuj i wklej następujący kod:

    // 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. Zastąp ciąg contoso nazwą dzierżawy. Wymagana nazwa zakresu można znaleźć zgodnie z opisem w artykule Konfigurowanie zakresów .

  4. Zastąp wartość parametru webApi lokalizacją punktu końcowego internetowego interfejsu API.

Krok 9. Wywoływanie internetowego interfejsu API

Zdefiniuj żądanie HTTP do punktu końcowego interfejsu API. Żądanie HTTP jest skonfigurowane do przekazania tokenu dostępu uzyskanego za pomocą MSAL.js do nagłówka Authorization HTTP w żądaniu.

Poniższy kod definiuje żądanie HTTP GET do punktu końcowego interfejsu API, przekazując token dostępu w nagłówku Authorization HTTP. Lokalizacja interfejsu API jest definiowana webApi przez klucz w apiConfig.js.

Aby wywołać internetowy interfejs API przy użyciu uzyskanego tokenu, wykonaj następujące czynności:

  1. W folderze App utwórz nowy plik o nazwie api.js.

  2. Dodaj następujący kod do pliku 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);
        });
    }
    

Krok 10. Dodawanie odwołania do elementów interfejsu użytkownika

Aplikacja SPA używa języka JavaScript do sterowania elementami interfejsu użytkownika. Na przykład wyświetla przyciski logowania i wylogowywanie oraz renderuje oświadczenia tokenu identyfikatora użytkownika na ekranie.

Aby dodać odwołanie do elementów interfejsu użytkownika, wykonaj następujące czynności:

  1. W folderze App utwórz plik o nazwie ui.js.

  2. Dodaj następujący kod do pliku 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'));
    }
    

Krok 11. Uruchamianie aplikacji SPA

W powłoce poleceń uruchom następujące polecenia:

npm install  
npm ./index.js
  1. Przejdź do strony https://localhost:6420.
  2. Wybierz pozycję Zaloguj się.
  3. Ukończ proces rejestracji lub logowania.

Po pomyślnym uwierzytelnieniu na ekranie zostanie wyświetlony przeanalizowany token identyfikatora. Wybierz Call API , aby wywołać punkt końcowy interfejsu API.

Następne kroki