Habilite a autenticação em seu próprio aplicativo de página única usando o Azure AD B2C

Este artigo mostra como adicionar a autenticação do Azure Active Directory B2C (Azure AD B2C) ao seu próprio SPA (aplicativo de página única). Saiba como criar um aplicativo SPA usando a Biblioteca de Autenticação da Microsoft para JavaScript (MSAL.js).

Use este artigo com o artigo Configurar a autenticação em um aplicativo de SPA de exemplo, substituindo o aplicativo SPA de exemplo pelo seu próprio aplicativo SPA.

Visão geral

Este artigo usa Node.js e Express para criar um aplicativo Web Node.js básico. Express é uma estrutura de aplicativo da web Node.js mínima e flexível que fornece um conjunto de recursos para aplicativos Web e móveis.

A biblioteca de autenticação MSAL.js é uma biblioteca fornecida pela Microsoft que simplifica a adição de suporte de autenticação e autorização a aplicativos SPA.

Dica

Todo o código MSAL.js é executado no lado do cliente. Você pode substituir os código Express e Node.js do lado do servidor por outras soluções como linguagens de script .NET Core, Java e PHP (Hypertext Preprocessor).

Pré-requisitos

Para revisar os pré-requisitos e as instruções de integração, consulte Configurar autenticação em um exemplo de aplicativo SPA.

Etapa 1: criar um projeto de aplicativo SPA

É possível usar um projeto de aplicativo SPA existente ou criar um novo. Para criar um novo projeto, faça o seguinte:

  1. Abra um shell de comando e crie um novo diretório (por exemplo, myApp). Este diretório conterá o código do aplicativo, a interface do usuário e os arquivos de configuração.

  2. Insira o diretório que você criou.

  3. Use o comando npm init para criar um arquivo package.json no seu aplicativo. Esse comando solicita informações sobre seu aplicativo (por exemplo, o nome e a versão do aplicativo e o nome do ponto de entrada inicial, o arquivo index.js). Execute o seguinte comando e aceite os valores padrão:

npm init

Etapa 2: instalar as dependências

Para instalar o pacote Express, em seu shell de comando, execute o seguinte comando:

npm install express

Para localizar os arquivos estáticos do aplicativo, o código do lado do servidor usa o pacote Path.

Para instalar o pacote Path, em seu shell de comando, execute o seguinte comando:

npm install path

Etapa 3: configure seu servidor Web

Na pasta myApp, crie um arquivo nomeado index.js, que contém o seguinte código:

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

Etapa 4: criar a interface do usuário de SPA

Adicione o arquivo de aplicativo index.html SPA. Este arquivo implementa uma interface do usuário construída com uma estrutura Bootstrap e importa os arquivos de script para configuração, autenticação e chamada à API Web.

Os recursos referenciados pelo arquivo index.html são detalhados na seguinte tabela:

Referência Definição
Biblioteca MSAL.js Caminho CDN da biblioteca JavaScript de autenticação MSAL.js.
Folha de estilos Bootstrap Uma estrutura de front-end gratuita para um desenvolvimento para a Web mais rápido e fácil. A estrutura inclui modelos de design baseados em HTML e CSS.
policies.js Contém as políticas personalizadas do Azure AD B2C e fluxos de usuário.
authConfig.js Contém parâmetros de configuração de autenticação.
authRedirect.js Contém a lógica de autenticação.
apiConfig.js Contém escopos de API Web e a localização do ponto de extremidade da API.
api.js Define o método a ser usado para chamar a API e tratar a resposta.
ui.js Controla os elementos de interface do usuário.

Para renderizar o arquivo de índice SPA, na pasta myApp, crie um arquivo nomeado como index.html, que contenha o seguinte trecho em 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>

Etapa 5: configurar a biblioteca de autenticação

Configure como a biblioteca MSAL.js se integra ao Azure AD B2C. A biblioteca MSAL.js usa um objeto de configuração comum para se conectar aos pontos de extremidade de autenticação do locatário do Azure AD B2C.

Para configurar a biblioteca de autenticação, faça o seguinte:

  1. Na pasta myApp, crie uma nova pasta nomeada Aplicativo.

  2. Na pasta Aplicativo, crie um novo arquivo nomeado authConfig.js.

  3. Adicione o seguinte código JavaScript ao arquivo 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. Substitua <Application-ID> pela ID do aplicativo de registro do seu aplicativo. Para obter mais informações, consulte Configurar autenticação em um exemplo de aplicativo SPA.

Dica

Para obter mais opções de configuração de objeto MSAL, consulte o artigoOpções de autenticação.

Etapa 6: especifique os fluxos de usuário do Azure AD B2C

Crie o arquivo policies.js, que fornece as informações sobre o ambiente do Azure AD B2C. A biblioteca MSAL.js usa essas informações para criar as solicitações de autenticação para o Azure AD B2C.

Para especificar os fluxos de usuário do Azure AD B2C, faça o seguinte:

  1. Na pasta Aplicativo, crie um novo arquivo nomeado policies.js.

  2. Adicione o código a seguir ao arquivo 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. Substitua B2C_1_SUSI pelo nome da sua política de entrada no Azure AD B2C.

  4. Substitua B2C_1_EditProfile pelo nome da política do Azure AD B2C do perfil de edição.

  5. Substitua todas as instâncias de contoso pelo seu nome do locatário do Azure AD B2C.

Etapa 7: usar MSAL para entrar no usuário

Nesta etapa, implemente os métodos para inicializar o fluxo de entrada, a aquisição do token de acesso de API e os métodos de saída.

Para obter mais informações, consulte o artigo Utilizar a MSAL (Microsoft Authentication Library) para iniciar sessão no utilizador.

Para entrar no usuário, faça o seguinte:

  1. Na pasta Aplicativo, crie um novo arquivo nomeado authRedirect.js.

  2. No authRedirect.js, copie e cole o seguinte código:

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

Etapa 8: configurar o escopo e a localização da API Web

Para permitir que o aplicativo SPA chame uma API Web, forneça a localização do ponto de extremidade da API Web e os scopes a serem usados para autorizar o acesso à API Web.

Para configurar os escopos e a localização da API Web, faça o seguinte:

  1. Na pasta Aplicativo, crie um novo arquivo nomeado apiConfig.js.

  2. No apiConfig.js, copie e cole o seguinte código:

    // 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. Substitua contoso pelo seu nome de locatário. O nome do escopo necessário pode ser encontrado conforme descrito no artigo Configurar escopos.

  4. Substitua o valor de webApi pela localização do ponto de extremidade da API Web.

Etapa 9: chamar sua API Web

Defina a solicitação HTTP para o seu ponto de extremidade de API. A solicitação HTTP é configurada para passar o token de acesso que foi adquirido com MSAL.js para o cabeçalho HTTP na solicitação Authorization.

O código a seguir define a solicitação GET HTTP para o ponto de extremidade da API, passando o token de acesso no cabeçalho HTTP Authorization. A localização da API é definida pela chave webApi em apiConfig.js.

Para chamar sua API Web usando o token adquirido, faça o seguinte:

  1. Na pasta Aplicativo, crie um novo arquivo nomeado api.js.

  2. Adicione o código a seguir ao arquivo 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);
        });
    }
    

Etapa 10: adicionar a referência de elementos de interface do usuário

O aplicativo SPA usa JavaScript para controlar os elementos de interface do usuário. Por exemplo, ele exibe os botões de entrada e saída e renderiza as declarações de token da ID dos usuários na tela.

Para adicionar a referência de elementos de interface do usuário, faça o seguinte:

  1. Na pasta Aplicativo, crie um novo arquivo nomeado ui.js.

  2. Adicione o código a seguir ao arquivo 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'));
    }
    

Etapa 11: executar seu aplicativo.

Em seu shell de comando, execute os seguintes comandos:

npm install  
npm ./index.js
  1. Acesse https://localhost:6420.
  2. Selecione Entrada.
  3. Conclua o processo de inscrição ou de entrada.

Após autenticar com sucesso, o token de ID analisado será exibido na tela. Selecione Call API para chamar seu ponto de extremidade de API.

Próximas etapas