Compartilhar via


Editar o perfil em um aplicativo Web Node.js

Aplica-se a: Círculo branco com um símbolo X cinza. Locatários da força de trabalho Círculo verde com um símbolo de marca de seleção branco. Locatários externos (saiba mais)

Esse artigo é a parte 2 de uma série que demonstra como adicionar a lógica de edição de perfil em um aplicativo Web Node.js. Na parte 1 dessa série, você configurou seu aplicativo para edição de perfil.

Neste guia de instruções, você aprenderá a chamar a API do Microsoft Graph para edição de perfil.

Pré-requisitos

Concluir o aplicativo Web cliente

Nesta seção, você adicionará o código relacionado à identidade para o aplicativo Web cliente.

Atualizar o arquivo authConfig.js

Atualize o arquivo authConfig.js para o aplicativo Web cliente:

  1. No seu editor de código, abra o arquivo App/authConfig.js, depois adicione três novas variáveis, GRAPH_API_ENDPOINT, GRAPH_ME_ENDPOINT e editProfileScope. Certifique-se de exportar as três variáveis:

    //...
    const GRAPH_API_ENDPOINT = process.env.GRAPH_API_ENDPOINT || "https://graph.microsoft.com/";
    // https://learn.microsoft.com/graph/api/user-update?tabs=http
    const GRAPH_ME_ENDPOINT = GRAPH_API_ENDPOINT + "v1.0/me";
    const editProfileScope = process.env.EDIT_PROFILE_FOR_CLIENT_WEB_APP || 'api://{clientId}/EditProfileService.ReadWrite';
    
    module.exports = {
        //...
        editProfileScope,
        GRAPH_API_ENDPOINT,
        GRAPH_ME_ENDPOINT,
        //...
    };
    
    • A variável editProfileScope representa o recurso protegido por MFA, que é o aplicativo de nível intermediário (aplicativo EditProfileService).

    • O GRAPH_ME_ENDPOINT é o ponto de extremidade da API do Microsoft Graph.

  2. Substitua o espaço reservado {clientId} pela ID do aplicativo (cliente) do aplicativo de nível intermediário (aplicativo EditProfileService) que você registrou anteriormente.

Adquirir o token de acesso no aplicativo Web cliente

No seu editor de código, abra o arquivo App/auth/AuthProvider.js, depois atualize o método getToken na classe AuthProvider:

    class AuthProvider {
    //...
        getToken(scopes, redirectUri = "http://localhost:3000/") {
            return  async function (req, res, next) {
                const msalInstance = authProvider.getMsalInstance(authProvider.config.msalConfig);
                try {
                    msalInstance.getTokenCache().deserialize(req.session.tokenCache);
    
                    const silentRequest = {
                        account: req.session.account,
                        scopes: scopes,
                    };
                    const tokenResponse = await msalInstance.acquireTokenSilent(silentRequest);
    
                    req.session.tokenCache = msalInstance.getTokenCache().serialize();
                    req.session.accessToken = tokenResponse.accessToken;
                    next();
                } catch (error) {
                    if (error instanceof msal.InteractionRequiredAuthError) {
                        req.session.csrfToken = authProvider.cryptoProvider.createNewGuid();
    
                        const state = authProvider.cryptoProvider.base64Encode(
                            JSON.stringify({
                                redirectTo: redirectUri,
                                csrfToken: req.session.csrfToken,
                            })
                        );
                        
                        const authCodeUrlRequestParams = {
                            state: state,
                            scopes: scopes,
                        };
    
                        const authCodeRequestParams = {
                            state: state,
                            scopes: scopes,
                        };
    
                        authProvider.redirectToAuthCodeUrl(
                            req,
                            res,
                            next,
                            authCodeUrlRequestParams,
                            authCodeRequestParams,
                            msalInstance
                        );
                    }
    
                    next(error);
                }
            };
        }
    }
    //...

O método getToken usa o escopo especificado para adquirir um token de acesso. O parâmetro redirectUri é a URL de redirecionamento depois que o aplicativo adquire um token de acesso.

Atualizar o arquivo users.js

No seu editor de código, abra o arquivo App/routes/users.js, depois adicione as seguintes rotas:

    //...
    
    var { fetch } = require("../fetch");
    const { GRAPH_ME_ENDPOINT, editProfileScope } = require('../authConfig');
    //...
    
router.get(
  "/gatedUpdateProfile",
  isAuthenticated,
  authProvider.getToken(["User.Read"]), // check if user is authenticated
  async function (req, res, next) {
    const graphResponse = await fetch(
      GRAPH_ME_ENDPOINT,
      req.session.accessToken,
    );
    if (!graphResponse.id) {
      return res 
        .status(501) 
        .send("Failed to fetch profile data"); 
    }
    res.render("gatedUpdateProfile", {
      profile: graphResponse,
    });
  },
);

router.get(
  "/updateProfile",
  isAuthenticated, // check if user is authenticated
  authProvider.getToken(
    ["User.Read", editProfileScope],
    "http://localhost:3000/users/updateProfile",
  ),
  async function (req, res, next) {
    const graphResponse = await fetch(
      GRAPH_ME_ENDPOINT,
      req.session.accessToken,
    );
    if (!graphResponse.id) {
      return res 
        .status(501) 
        .send("Failed to fetch profile data"); 
    }
    res.render("updateProfile", {
      profile: graphResponse,
    });
  },
);

router.post(
  "/update",
  isAuthenticated,
  authProvider.getToken([editProfileScope]),
  async function (req, res, next) {
    try {
      if (!!req.body) {
        let body = req.body;
        fetch(
          "http://localhost:3001/updateUserInfo",
          req.session.accessToken,
          "POST",
          {
            displayName: body.displayName,
            givenName: body.givenName,
            surname: body.surname,
          },
        )
          .then((response) => {
            if (response.status === 204) {
              return res.redirect("/");
            } else {
              next("Not updated");
            }
          })
          .catch((error) => {
            console.log("error,", error);
          });
      } else {
        throw { error: "empty request" };
      }
    } catch (error) {
      next(error);
    }
  },
);
    //...
  • Você disparará a rota /gatedUpdateProfile quando o usuário cliente seleciona o link Edição de perfil. O aplicativo:

    1. Adquirir um token de acesso com a permissão User.Read.
    2. Faz uma chamada à API do Microsoft Graph para ler o perfil do usuário conectado.
    3. Exibe os detalhes do usuário na interface do usuário gatedUpdateProfile.hbs.
  • Você acionará a rota /updateProfile quando o usuário desejar atualizar o nome de exibição, ou seja, selecionar o botão Editar perfil. O aplicativo:

    1. Faz uma chamada para o aplicativo de nível intermediário (aplicativo EditProfileService) usando o escopo editProfileScope. Ao fazer uma chamada para o aplicativo de nível intermediário (aplicativo EditProfileService), o usuário deverá concluir um desafio de MFA se ainda não tiver feito isso.
    2. Exibe os detalhes do usuário na interface do usuário updateProfile.hbs.
  • Dispare a rota /update quando o usuário selecionar o botão Salvar em gatedUpdateProfile.hbs ou updateProfile.hbs. O aplicativo:

    1. Recupera o token de acesso para a sessão do aplicativo. Você aprenderá como o aplicativo de nível intermediário (aplicativo EditProfileService) adquire o token de acesso na próxima seção.
    2. Coleta todos os detalhes do usuário.
    3. Faz uma chamada à API do Microsoft Graph para atualizar o perfil do usuário.

Atualizar o arquivo fetch.js

O aplicativo usa o arquivo App/fetch.js para fazer as chamadas à API reais.

No seu editor de código, abra o arquivo App/fetch.js, depois adicione a opção de operação PATCH. Após atualizar o arquivo, o arquivo resultante deverá ser semelhante ao seguinte código:

var axios = require('axios');
var authProvider = require("./auth/AuthProvider");

/**
 * Makes an Authorization "Bearer" request with the given accessToken to the given endpoint.
 * @param endpoint
 * @param accessToken
 * @param method
 */
const fetch = async (endpoint, accessToken, method = "GET", data = null) => {
    const options = {
        headers: {
            Authorization: `Bearer ${accessToken}`,
        },
    };
    console.log(`request made to ${endpoint} at: ` + new Date().toString());

    switch (method) {
        case 'GET':
            const response = await axios.get(endpoint, options);
            return await response.data;
        case 'POST':
            return await axios.post(endpoint, data, options);
        case 'DELETE':
            return await axios.delete(endpoint + `/${data}`, options);
        case 'PATCH': 
            return await axios.patch(endpoint, ReqBody = data, options);
        default:
            return null;
    }
};

module.exports = { fetch };

Concluir o aplicativo de nível intermediário

Nesta seção, você adicionará o código relacionado à identidade para o aplicativo de nível intermediário (aplicativo EditProfileService).

  1. No seu editor de código, abra o arquivo Api/authConfig.js, depois adicione o seguinte código:

    require("dotenv").config({ path: ".env.dev" });
    
    const TENANT_SUBDOMAIN =
      process.env.TENANT_SUBDOMAIN || "Enter_the_Tenant_Subdomain_Here";
    const TENANT_ID = process.env.TENANT_ID || "Enter_the_Tenant_ID_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";
    
    /**
     * 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_Edit_Profile_Service_Application_Id_Here", // 'Application (client) ID' of the Edit_Profile Service App registration in Microsoft Entra admin center - this value is a GUID
        authority:
          process.env.AUTHORITY || `https://${TENANT_SUBDOMAIN}.ciamlogin.com/`, // Replace the placeholder with your external tenant name
      },
      system: {
        loggerOptions: {
          loggerCallback(loglevel, message, containsPii) {
            console.log(message);
          },
          piiLoggingEnabled: false,
          logLevel: "Info",
        },
      },
    };
    
    const GRAPH_API_ENDPOINT = process.env.GRAPH_API_ENDPOINT || "graph_end_point";
    // Refers to the user that is single user singed in.
    // https://learn.microsoft.com/en-us/graph/api/user-update?tabs=http
    const GRAPH_ME_ENDPOINT = GRAPH_API_ENDPOINT + "v1.0/me";
    
    module.exports = {
      msalConfig,
      REDIRECT_URI,
      POST_LOGOUT_REDIRECT_URI,
      TENANT_SUBDOMAIN,
      GRAPH_API_ENDPOINT,
      GRAPH_ME_ENDPOINT,
      TENANT_ID,
    };
    

    Localize o espaço reservado:

    • Enter_the_Tenant_Subdomain_Here e substitua-o pelo subdomínio do Diretório (locatário). Por exemplo, se o domínio primário do locatário for contoso.onmicrosoft.com, use contoso. Se você não tiver o nome do locatário, saiba como ler os detalhes do locatário.
    • Enter_the_Tenant_ID_Here e substitua-o pela ID do Locatário. Se você não tiver sua ID do Locatário, saiba como ler os detalhes do seu locatário.
    • Enter_the_Edit_Profile_Service_Application_Id_Here e substitua-o pelo valor da ID do aplicativo (cliente) do EditProfileService que você registrou anteriormente.
    • Enter_the_Client_Secret_Here e substitua-o pelo valor do segredo do aplicativo EditProfileService que você copiou anteriormente.
    • graph_end_point e substitua pelo ponto de extremidade da API do Microsoft Graph, ou seja https://graph.microsoft.com/.
  2. No seu editor de código, abra o arquivo Api/fetch.js, depois cole o código do arquivo Api/fetch.js. A função fetch usa um token de acesso e o ponto de extremidade do recurso para fazer a chamada à API real.

  3. No seu editor de código, abra o arquivo Api/index.js, depois cole o código do arquivo Api/index.js.

Adquirir um token de acesso usando acquireTokenOnBehalfOf

No arquivo Api/index.js, o aplicativo de nível intermediário (aplicativo EditProfileService) adquire um token de acesso usando a função acquireTokenOnBehalfOf, que ele usa para atualizar o perfil em nome desse usuário.

async function getAccessToken(tokenRequest) {
  try {
    const response = await cca.acquireTokenOnBehalfOf(tokenRequest);
    return response.accessToken;
  } catch (error) {
    console.error("Error acquiring token:", error);
    throw error;
  }
}

O parâmetro tokenRequest é definido conforme mostrado no código a seguir:

    const tokenRequest = {
      oboAssertion: req.headers.authorization.replace("Bearer ", ""),
      authority: `https://${TENANT_SUBDOMAIN}.ciamlogin.com/${TENANT_ID}`,
      scopes: ["User.ReadWrite"],
      correlationId: `${uuidv4()}`,
    };

No mesmo arquivo, API/index.js, o aplicativo de nível intermediário (aplicativo EditProfileService) faz uma chamada à API do Microsoft Graph para atualizar o perfil dos usuários:

   let accessToken = await getAccessToken(tokenRequest);
    fetch(GRAPH_ME_ENDPOINT, accessToken, "PATCH", req.body)
      .then((response) => {
        if (response.status === 204) {
          res.status(response.status);
          res.json({ message: "Success" });
        } else {
          res.status(502);
          res.json({ message: "Failed, " + response.body });
        }
      })
      .catch((error) => {
        res.status(502);
        res.json({ message: "Failed, " + error });
      });

Testar seu aplicativo

Para testar seu aplicativo, use as seguintes etapas:

  1. Para executar o aplicativo cliente, a partir da janela do terminal, navegue até o diretório App, depois execute o seguinte comando:

    npm start
    
  2. Para executar o aplicativo cliente, a partir da janela do terminal, navegue até o diretório API, depois execute o seguinte comando:

    npm start
    
  3. Abra o navegador e acesse http://localhost:3000. Se houver erros de certificado SSL, crie um arquivo .env e adicione a seguinte configuração:

    # Use this variable only in the development environment. 
    # Remove the variable when you move the app to the production environment.
    NODE_TLS_REJECT_UNAUTHORIZED='0'
    
  4. Selecione o botão Entrar e, em seguida, conecte-se.

  5. Na página de entrada, digite seu Endereço de email, selecione Avançar, digite sua Senha e selecione Entrar. Se você não tiver uma conta, selecione o link Não tem uma conta? Crie uma, que iniciará o fluxo de inscrição.

  6. Para atualizar o perfil, selecione o link Edição de perfil. Você verá uma página semelhante à seguinte captura de tela:

    Captura de tela do perfil de atualização do usuário.

  7. Para editar o perfil, selecione o botão Editar Perfil. Se você ainda não fez isso, o aplicativo solicitará que você conclua um desafio de MFA.

  8. Faça alterações em qualquer um dos detalhes do perfil, depois selecione o botão Salvar.