Partager via


Activer l’authentification dans votre propre application monopage en utilisant Azure Active Directory B2C

Cet article explique comment ajouter l’authentification Azure Active Directory B2C (Azure AD B2C) à votre propre application monopage (SPA). Découvrez comment créer une application SPA en utilisant la bibliothèque d’authentification Microsoft pour JavaScript (MSAL.js).

Utilisez cet article avec l’article Configuration de l’authentification dans un exemple d’application SPA, en remplaçant l’exemple d’application SPA par votre propre application SPA.

Vue d'ensemble

Cet article utilise Node.js et Express pour créer une application web Node.js de base. Express est une infrastructure d’applications web Node.js flexible et minimale qui fournit un ensemble de fonctionnalités aux applications web et mobiles.

La bibliothèque d’authentification MSAL.js est une bibliothèque fournie par Microsoft qui simplifie l’ajout d’une prise en charge de l’authentification et de l’autorisation aux applications SPA.

Conseil

L’intégralité du code MSAL.js s’exécute côté client. Vous pouvez remplacer le Node.js et le code côté serveur Express par d’autres solutions, telles que les langages de script .NET Core, Java et PHP (Hypertext Preprocesseur).

Configuration requise

Pour passer en revue les conditions préalables et les instructions d’intégration, consultez Configurer l’authentification dans un exemple d’application SPA.

Étape 1 : créer un projet d’application SPA

Vous pouvez utiliser un projet d’application SPA existant ou en créer un. Pour créer un projet, procédez de la façon suivante :

  1. Ouvrez un interpréteur de commandes, puis créez un répertoire (par exemple, myApp). Ce répertoire contient le code de votre application, l’interface utilisateur et les fichiers de configuration.

  2. Accédez au répertoire que vous avez créé.

  3. Utilisez la commande npm init pour créer un fichier package.json pour votre application. Cette commande vous invite à entrer des informations sur votre application (par exemple, le nom et la version de votre application, ainsi que le nom du point d’entrée initial, le fichier index.js). Exécutez la commande suivante et acceptez les valeurs par défaut :

npm init

Étape 2 : installer les dépendances

Pour installer le package Express, dans votre interpréteur de commandes, exécutez la commande suivante :

npm install express

Pour localiser les fichiers statiques de l’application, le code côté serveur utilise le package Path.

Pour installer le package Path, dans votre interpréteur de commandes, exécutez la commande suivante :

npm install path

Étape 3 : configurer votre serveur web

Dans votre dossier myApp, créez un fichier nommé index.js, qui contient le code suivant :

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

Étape 4 : créer l’interface utilisateur de SPA

Ajoutez le fichier d’application index.html SPA. Ce fichier implémente une interface utilisateur qui est générée avec une infrastructure de démarrage et il importe des fichiers de script pour la configuration, l’authentification et les appels d’API web.

Les ressources référencées par le fichier index.html sont détaillées dans le tableau suivant :

Référence Définition
Bibliothèque MSAL.js Chemin CDN de la bibliothèque JavaScript d’authentification MSAL.js.
Feuille de style de démarrage Une infrastructure frontale gratuite pour un développement web plus rapide et plus facile. L’infrastructure comprend des modèles de conception basés sur HTML et CSS.
policies.js Contient les stratégies personnalisées Azure AD B2C et les flux utilisateur.
authConfig.js Contient les paramètres de configuration de l’authentification.
authRedirect.js Contient la logique d'authentification.
apiConfig.js Contient les étendues de l’API web et l’emplacement du point de terminaison de l’API.
api.js Définit la méthode à utiliser pour appeler votre API et gérer sa réponse.
ui.js Contrôle les éléments d’interface utilisateur.

Pour afficher le fichier d’index SPA, dans le dossier myApp, créez un fichier nommé index.html, qui contient l’extrait HTML suivant :

<!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>

Étape 5 : configurer la bibliothèque d’authentification

Configurez le mode d’intégration de la bibliothèque MSAL.js avec Azure AD B2C. La bibliothèque MSAL.js utilise un objet de configuration courant pour se connecter aux points de terminaison d’authentification de votre locataire Azure AD B2C.

Pour configurer la bibliothèque d’authentification, effectuez ce qui suit :

  1. Dans le dossier myApp, créez un dossier appelé App.

  2. Dans le dossier app, créez un fichier nommé authConfig.js.

  3. Ajoutez le code JavaScript suivant au fichier 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. Remplacez <Application-ID> par l'ID d'application pour l'inscription de votre application. Pour plus d’informations, consultez Configurer l’authentification dans un exemple d’application SPA.

Conseil

Pour plus d’options de configuration d’objet MSAL, reportez-vous à l’article Options d’authentification.

Étape 6 : spécifier vos flux d’utilisateurs Azure AD B2C

Créez le fichier policies.js, qui fournit des informations sur votre environnement Azure AD B2C. La bibliothèque MSAL.js utilise ces informations pour créer des requêtes d’authentification à Azure AD B2C.

Pour spécifier vos flux d’utilisateur Azure AD B2C, procédez comme suit :

  1. Dans le dossier App, créez un fichier nommé policies.js.

  2. Ajoutez le code suivant au fichier 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. Remplacez B2C_1_SUSI par le nom de votre stratégie Azure AD B2C de connexion.

  4. Remplacez B2C_1_EditProfile par le nom de votre stratégie Azure AD B2C de profil de modification.

  5. Remplacez toutes les instances de contoso par le nom de votre locataire Azure AD B2C.

Étape 7 : utiliser MSAL pour la connexion de l’utilisateur

Dans cette étape, implémentez les méthodes pour initialiser le flux de connexion, l’acquisition du jeton d’accès de l’API et les méthodes de déconnexion.

Pour plus d’informations, consultez l’article Utiliser la bibliothèque d’authentification Microsoft (MSAL) pour connecter l’utilisateur.

Pour connecter l’utilisateur, procédez comme suit :

  1. Dans le dossier App, créez un fichier nommé authRedirect.js.

  2. Dans votre authRedirect.js, copiez et collez le code suivant :

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

Étape 8 : configurer l’emplacement et l’étendue de l’API web

Pour permettre à votre application SPA d’appeler une API web, indiquez l’emplacement du point de terminaison de l’API web et les étendues à utiliser pour autoriser l’accès à l’API web.

Pour configurer l’emplacement et les étendues de l’API web, procédez comme suit :

  1. Dans le dossier App, créez un fichier nommé apiConfig.js.

  2. Dans votre apiConfig.js, copiez et collez le code suivant :

    // 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. Remplacez contoso par votre nom de locataire. Le nom de l’étendue requis peut se trouvé comme décrit dans l’article Configurer les étendues.

  4. Remplacez la valeur pour webApi par l’emplacement de votre point de terminaison d’API web.

Étape 9 : appeler votre API web

Définissez la requête HTTP sur votre point de terminaison d’API. La requête HTTP est configurée pour transmettre le jeton d’accès acquis avec MSAL.js dans l’en-tête HTTP Authorization dans la requête.

Le code suivant définit la requête GET HTTP sur le point de terminaison de l’API, transmettant le jeton d’accès dans l’en-tête HTTP Authorization. L’emplacement de l’API est défini par la clé webApi dans apiConfig.js.

Pour appeler votre API web à l’aide du jeton que vous avez acquis, procédez comme suit :

  1. Dans le dossier App, créez un fichier nommé api.js.

  2. Ajoutez le code suivant au fichier 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);
        });
    }
    

Étape 10 : ajouter la référence des éléments d’interface utilisateur

L’application SPA utilise JavaScript pour contrôler les éléments d’interface utilisateur. Par exemple, elle affiche les boutons de connexion et de déconnexion, et affiche les revendications de jeton d’ID d’utilisateur à l’écran.

Pour ajouter la référence des éléments d’interface utilisateur, procédez comme suit :

  1. Dans le dossier App, créez un fichier nommé ui.js.

  2. Ajoutez le code suivant au fichier 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'));
    }
    

Étape 11 : exécuter votre application SPA

Dans votre interpréteur de commandes, exécutez les commandes suivantes :

npm install  
npm ./index.js
  1. Accédez à https://localhost:6420.
  2. Sélectionnez Connexion.
  3. Terminez le processus d’inscription ou de connexion.

Une fois l’authentification réussie, le jeton d’ID analysé s’affiche à l’écran. Sélectionnez Call API pour appeler le point de terminaison de votre API.

Étapes suivantes