Compartilhar via


Tutorial: Adicionar a entrada a um aplicativo Web Node/Express.js usando a plataforma de identidade da Microsoft

Aplica-se a: Círculo verde com um símbolo de marca de seleção branca que indica que o conteúdo a seguir se aplica aos locatários da força de trabalho. Locatários da força de trabalho Círculo verde com um símbolo de marca de seleção branca que indica que o conteúdo a seguir se aplica a locatários externos. Locatários externos (saiba mais)

Neste tutorial, você adicionará a lógica de entrada e saída ao aplicativo Web Node/Express. Esse código permite que você conecte usuários em seu aplicativo voltado para o cliente em um locatário externo ou funcionários em um locatário da força de trabalho.

Este tutorial é a parte 2 da série de tutoriais de 3 partes.

Neste tutorial, você:

  • Adicionar lógica de entrada e saída
  • Exibir as declarações de token da ID
  • Execute o aplicativo e teste a experiência de entrada e saída.

Pré-requisitos

Criar objeto de configuração da MSAL

No editor de código, abra o arquivo authConfig.js e adicione o seguinte código:

require('dotenv').config();

const TENANT_SUBDOMAIN = process.env.TENANT_SUBDOMAIN || 'Enter_the_Tenant_Subdomain_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';
const GRAPH_ME_ENDPOINT = process.env.GRAPH_API_ENDPOINT + "v1.0/me" || 'Enter_the_Graph_Endpoint_Here';

/**
 * 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_Application_Id_Here', // 'Application (client) ID' of app registration in Azure portal - this value is a GUID
        //For external tenant
        authority: process.env.AUTHORITY || `https://${TENANT_SUBDOMAIN}.ciamlogin.com/`, // replace "Enter_the_Tenant_Subdomain_Here" with your tenant name
        //For workforce tenant
        //authority: process.env.CLOUD_INSTANCE + process.env.TENANT_ID
        clientSecret: process.env.CLIENT_SECRET || 'Enter_the_Client_Secret_Here', // Client secret generated from the app registration in Azure portal
    },
    system: {
        loggerOptions: {
            loggerCallback(loglevel, message, containsPii) {
                console.log(message);
            },
            piiLoggingEnabled: false,
            logLevel: 'Info',
        },
    },
};

module.exports = {
    msalConfig,
    REDIRECT_URI,
    POST_LOGOUT_REDIRECT_URI,
    TENANT_SUBDOMAIN,
    GRAPH_ME_ENDPOINT
};

O objeto msalConfig contém um conjunto de opções de configuração que você usará para personalizar o comportamento dos seus fluxos de autenticação.

No arquivo authConfig.js, substitua:

  • Enter_the_Application_Id_Here com a ID do aplicativo (cliente) referente ao aplicativo registrado anteriormente.

  • Enter_the_Tenant_Subdomain_Here e substitua-a pelo subdomínio do Diretório(locatário) externo. 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. Esse valor é necessário apenas para o locatário externo.

  • Enter_the_Client_Secret_Here com o valor do segredo do aplicativo copiado anteriormente.

  • Enter_the_Graph_Endpoint_Here com a instância de nuvem da API do Microsoft Graph que seu aplicativo chamará. Use o valor https://graph.microsoft.com/ (inclua a barra à direita)

Se você usar o arquivo .env para armazenar suas informações de configuração:

  1. No editor de código, abra o arquivo .env e adicione o código a seguir.

        CLIENT_ID=Enter_the_Application_Id_Here
        TENANT_SUBDOMAIN=Enter_the_Tenant_Subdomain_Here 
        CLOUD_INSTANCE="Enter_the_Cloud_Instance_Id_Here" # cloud instance string should end with a trailing slash
        TENANT_ID=Enter_the_Tenant_ID_here
        CLIENT_SECRET=Enter_the_Client_Secret_Here
        REDIRECT_URI=http://localhost:3000/auth/redirect
        POST_LOGOUT_REDIRECT_URI=http://localhost:3000
        GRAPH_API_ENDPOINT=Enter_the_Graph_Endpoint_Here # graph api endpoint string should end with a trailing slash
        EXPRESS_SESSION_SECRET=Enter_the_Express_Session_Secret_Here # express session secret, just any random text
    
  2. Substitua o espaço reservado:

    1. Enter_the_Application_Id_Here, Enter_the_Tenant_Subdomain_Here e Enter_the_Client_Secret_Here como explicado anteriormente.
    2. Enter_the_Cloud_Instance_Id_Here com a instância de nuvem do Azure na qual seu aplicativo está registrado. Use https://login.microsoftonline.com/ como seu valor (inclua a barra à direita). Esse valor é necessário apenas para o locatário da força de trabalho.
    3. Enter_the_Tenant_ID_here com a ID do locatário da força de trabalho ou domínio primário, como ou aaaabbbb-0000-cccc-1111-dddd2222eeee ou contoso.microsoft.com. Esse valor é necessário apenas para o locatário da força de trabalho.

Você deve exportar as variáveis msalConfig, REDIRECT_URI, TENANT_SUBDOMAIN, GRAPH_ME_ENDPOINT e POST_LOGOUT_REDIRECT_URI no arquivo authConfig.js para torná-las acessíveis em outros arquivos.

URL de autoridade para seu aplicativo

As autoridades de aplicativo para locatários externos e da força de trabalho parecem diferentes. Compile-os conforme mostrado abaixo:

//Authority for workforce tenant
authority: process.env.CLOUD_INSTANCE + process.env.TENANT_ID

Usar domínio de URL personalizado (opcional)

Não há suporte para os domínios da URL personalizados nos locatários da força de trabalho.

Adicionar rotas expressas

As rotas Express fornecem os pontos de extremidade que nos permitem executar operações como entrar, sair e exibir declarações de token da ID.

Ponto de entrada do aplicativo

No editor de código, abra o arquivo routes/index.js e adicione o seguinte código:

const express = require('express');
const router = express.Router();

router.get('/', function (req, res, next) {
    res.render('index', {
        title: 'MSAL Node & Express Web App',
        isAuthenticated: req.session.isAuthenticated,
        username: req.session.account?.username !== '' ? req.session.account?.username : req.session.account?.name,
    });
});    
module.exports = router;

A rota / é o ponto de entrada para o aplicativo. Ele renderiza a exibição views/index.hbs que você criou anteriormente em componentes da interface do usuário do aplicativo Build. isAuthenticated é uma variável booliana que determina o que você vê na exibição.

Entrar e sair

  1. No editor de código, abra rotas/auth.js arquivo e adicione o seguinte código:

    const express = require('express');
    const authController = require('../controller/authController');
    const router = express.Router();
    
    router.get('/signin', authController.signIn);
    router.get('/signout', authController.signOut);
    router.post('/redirect', authController.handleRedirect);
    
    module.exports = router;
    
  2. No editor de código, abra o controlador/authController.js arquivo e adicione o seguinte código:

    const authProvider = require('../auth/AuthProvider');
    
    exports.signIn = async (req, res, next) => {
        return authProvider.login(req, res, next);
    };
    
    exports.handleRedirect = async (req, res, next) => {
        return authProvider.handleRedirect(req, res, next);
    }
    
    exports.signOut = async (req, res, next) => {
        return authProvider.logout(req, res, next);
    };
    
    
  3. No editor de código, abra o arquivo de autenticação/AuthProvider.js e adicione o seguinte código:

    const msal = require('@azure/msal-node');
    const axios = require('axios');
    const { msalConfig, TENANT_SUBDOMAIN, REDIRECT_URI, POST_LOGOUT_REDIRECT_URI, GRAPH_ME_ENDPOINT} = require('../authConfig');
    
    class AuthProvider {
        config;
        cryptoProvider;
    
        constructor(config) {
            this.config = config;
            this.cryptoProvider = new msal.CryptoProvider();
        }
    
        getMsalInstance(msalConfig) {
            return new msal.ConfidentialClientApplication(msalConfig);
        }
    
        async login(req, res, next, options = {}) {
            // create a GUID for crsf
            req.session.csrfToken = this.cryptoProvider.createNewGuid();
    
            /**
             * The MSAL Node library allows you to pass your custom state as state parameter in the Request object.
             * The state parameter can also be used to encode information of the app's state before redirect.
             * You can pass the user's state in the app, such as the page or view they were on, as input to this parameter.
             */
            const state = this.cryptoProvider.base64Encode(
                JSON.stringify({
                    csrfToken: req.session.csrfToken,
                    redirectTo: '/',
                })
            );
    
            const authCodeUrlRequestParams = {
                state: state,
    
                /**
                 * By default, MSAL Node will add OIDC scopes to the auth code url request. For more information, visit:
                 * https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
                 */
                scopes: [],
            };
    
            const authCodeRequestParams = {
                state: state,
    
                /**
                 * By default, MSAL Node will add OIDC scopes to the auth code request. For more information, visit:
                 * https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
                 */
                scopes: [],
            };
    
            /**
             * If the current msal configuration does not have cloudDiscoveryMetadata or authorityMetadata, we will
             * make a request to the relevant endpoints to retrieve the metadata. This allows MSAL to avoid making
             * metadata discovery calls, thereby improving performance of token acquisition process.
             */
            if (!this.config.msalConfig.auth.authorityMetadata) {
                const authorityMetadata = await this.getAuthorityMetadata();
                this.config.msalConfig.auth.authorityMetadata = JSON.stringify(authorityMetadata);
            }
    
            const msalInstance = this.getMsalInstance(this.config.msalConfig);
    
            // trigger the first leg of auth code flow
            return this.redirectToAuthCodeUrl(
                req,
                res,
                next,
                authCodeUrlRequestParams,
                authCodeRequestParams,
                msalInstance
            );
        }
    
        async handleRedirect(req, res, next) {
            const authCodeRequest = {
                ...req.session.authCodeRequest,
                code: req.body.code, // authZ code
                codeVerifier: req.session.pkceCodes.verifier, // PKCE Code Verifier
            };
    
            try {
                const msalInstance = this.getMsalInstance(this.config.msalConfig);
                msalInstance.getTokenCache().deserialize(req.session.tokenCache);
    
                const tokenResponse = await msalInstance.acquireTokenByCode(authCodeRequest, req.body);
    
                req.session.tokenCache = msalInstance.getTokenCache().serialize();
                req.session.idToken = tokenResponse.idToken;
                req.session.account = tokenResponse.account;
                req.session.isAuthenticated = true;
    
                const state = JSON.parse(this.cryptoProvider.base64Decode(req.body.state));
                res.redirect(state.redirectTo);
            } catch (error) {
                next(error);
            }
        }
    
        async logout(req, res, next) {
            /**
             * Construct a logout URI and redirect the user to end the
             * session with Microsoft Entra ID. For more information, visit:
             * https://docs.microsoft.com/azure/active-directory/develop/v2-protocols-oidc#send-a-sign-out-request
             */
            //For external tenant
            //const logoutUri = `${this.config.msalConfig.auth.authority}${TENANT_SUBDOMAIN}.onmicrosoft.com/oauth2/v2.0/logout?post_logout_redirect_uri=${this.config.postLogoutRedirectUri}`;
    
            //For workforce tenant
            let logoutUri = `${this.config.msalConfig.auth.authority}/oauth2/v2.0/logout?post_logout_redirect_uri=${this.config.postLogoutRedirectUri}`;
            req.session.destroy(() => {
                res.redirect(logoutUri);
            });
        }
    
        /**
         * Prepares the auth code request parameters and initiates the first leg of auth code flow
         * @param req: Express request object
         * @param res: Express response object
         * @param next: Express next function
         * @param authCodeUrlRequestParams: parameters for requesting an auth code url
         * @param authCodeRequestParams: parameters for requesting tokens using auth code
         */
        async redirectToAuthCodeUrl(req, res, next, authCodeUrlRequestParams, authCodeRequestParams, msalInstance) {
            // Generate PKCE Codes before starting the authorization flow
            const { verifier, challenge } = await this.cryptoProvider.generatePkceCodes();
    
            // Set generated PKCE codes and method as session vars
            req.session.pkceCodes = {
                challengeMethod: 'S256',
                verifier: verifier,
                challenge: challenge,
            };
    
            /**
             * By manipulating the request objects below before each request, we can obtain
             * auth artifacts with desired claims. For more information, visit:
             * https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_node.html#authorizationurlrequest
             * https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_node.html#authorizationcoderequest
             **/
    
            req.session.authCodeUrlRequest = {
                ...authCodeUrlRequestParams,
                redirectUri: this.config.redirectUri,
                responseMode: 'form_post', // recommended for confidential clients
                codeChallenge: req.session.pkceCodes.challenge,
                codeChallengeMethod: req.session.pkceCodes.challengeMethod,
            };
    
            req.session.authCodeRequest = {
                ...authCodeRequestParams,
                redirectUri: this.config.redirectUri,
                code: '',
            };
    
            try {
                const authCodeUrlResponse = await msalInstance.getAuthCodeUrl(req.session.authCodeUrlRequest);
                res.redirect(authCodeUrlResponse);
            } catch (error) {
                next(error);
            }
        }
    
        /**
         * Retrieves oidc metadata from the openid endpoint
         * @returns
         */
        async getAuthorityMetadata() {
            // For external tenant
            //const endpoint = `${this.config.msalConfig.auth.authority}${TENANT_SUBDOMAIN}.onmicrosoft.com/v2.0/.well-known/openid-configuration`;
    
            // For workforce tenant
            const endpoint = `${this.config.msalConfig.auth.authority}/v2.0/.well-known/openid-configuration`;
            try {
                const response = await axios.get(endpoint);
                return await response.data;
            } catch (error) {
                console.log(error);
            }
        }
    }
    
    const authProvider = new AuthProvider({
        msalConfig: msalConfig,
        redirectUri: REDIRECT_URI,
        postLogoutRedirectUri: POST_LOGOUT_REDIRECT_URI,
    });
    
    module.exports = authProvider;
    
    

    /signin, /signout e /redirect as rotas são definidas no arquivo routes/auth.js, mas você implementa sua lógica na classe auth/AuthProvider.js.

  • O método login manipula a rota /signin:

    • Ele inicia o fluxo de entrada disparando a primeira etapa do fluxo de código de autenticação.

    • Ele inicializa uma instância de aplicativo cliente confidencial usando o objeto de configuração MSAL, msalConfigque você criou anteriormente.

          const msalInstance = this.getMsalInstance(this.config.msalConfig);
      

      O getMsalInstance método é definido como:

          getMsalInstance(msalConfig) {
              return new msal.ConfidentialClientApplication(msalConfig);
          }
      
    • A primeira etapa do fluxo de código de autenticação gera uma URL de solicitação de código de autorização e redireciona para essa URL para obter o código de autorização. Esta primeira etapa é implementada no redirectToAuthCodeUrl método. Observe como usamos o método getAuthCodeUrl das MSALs para gerar a URL do código de autorização:

      //...
      const authCodeUrlResponse = await msalInstance.getAuthCodeUrl(req.session.authCodeUrlRequest);
      //...
      

      Em seguida, redirecionamos para a URL do código de autorização em si.

      //...
      res.redirect(authCodeUrlResponse);
      //...
      
  • O método handleRedirect manipula a rota /redirect:

    • Você define essa URL como URI de Redirecionamento para o aplicativo Web no Centro de administração do Microsoft Entra anteriormente no Início Rápido: Conectar usuários em um aplicativo Web de exemplo.

    • Esse ponto de extremidade implementa a segunda etapa de uso do fluxo de código de autenticação. Ele usa o código de autorização para solicitar um token de ID usando o método acquireTokenByCode da MSAL.

      //...
      const tokenResponse = await msalInstance.acquireTokenByCode(authCodeRequest, req.body);
      //...
      
    • Depois de receber uma resposta, você pode criar uma sessão Expresso e armazenar as informações desejadas nela. Você precisa incluir isAuthenticated e defini-lo como true:

      //...        
      req.session.idToken = tokenResponse.idToken;
      req.session.account = tokenResponse.account;
      req.session.isAuthenticated = true;
      //...
      
  • O método logout manipula a rota /signout:

    async logout(req, res, next) {
        /**
         * Construct a logout URI and redirect the user to end the
            * session with Azure AD. For more information, visit:
            * https://docs.microsoft.com/azure/active-directory/develop/v2-protocols-oidc#send-a-sign-out-request
            */
        const logoutUri = `${this.config.msalConfig.auth.authority}${TENANT_SUBDOMAIN}.onmicrosoft.com/oauth2/v2.0/logout?post_logout_redirect_uri=${this.config.postLogoutRedirectUri}`;
    
        req.session.destroy(() => {
            res.redirect(logoutUri);
        });
    }
    
    • Inicia a solicitação de logout.

    • Quando você deseja desconectar o usuário do aplicativo, não é suficiente encerrar a sessão do usuário. Você deve redirecionar o usuário para o logoutUri. Caso contrário, o usuário pode ser capaz de autenticar novamente seus aplicativos sem inserir novamente as próprias credenciais. Se o nome do seu locatário for contoso, logoutUri será semelhante a https://contoso.ciamlogin.com/contoso.onmicrosoft.com/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000.

URI de logoff e ponto de extremidade de metadados de autoridade para seu aplicativo

O URI de logoff do aplicativo, logoutUri e o ponto de extremidade dos metadados da autoridade, endpoint para locatários externos e da força de trabalho parecem diferentes. Compile-os conforme mostrado abaixo:

//Logout URI for workforce tenant
const logoutUri = `${this.config.msalConfig.auth.authority}/oauth2/v2.0/logout?post_logout_redirect_uri=${this.config.postLogoutRedirectUri}`;

//authority metadata endpoint for workforce tenant
const endpoint = `${this.config.msalConfig.auth.authority}/v2.0/.well-known/openid-configuration`;

Exibir as declarações de token da ID

No editor de código, abra o arquivo routes/users.js e adicione o seguinte código:

const express = require('express');
const router = express.Router();

// custom middleware to check auth state
function isAuthenticated(req, res, next) {
    if (!req.session.isAuthenticated) {
        return res.redirect('/auth/signin'); // redirect to sign-in route
    }

    next();
};

router.get('/id',
    isAuthenticated, // check if user is authenticated
    async function (req, res, next) {
        res.render('id', { idTokenClaims: req.session.account.idTokenClaims });
    }
);        
module.exports = router;

Se o usuário for autenticado, a rota /id exibirá declarações de token da ID usando a exibição views/id.hbs. Você adicionou essa exibição anteriormente em Compilar componentes da interface do usuário do aplicativo.

Para extrair uma declaração de token da ID específica, como um determinado nome:

const givenName = req.session.account.idTokenClaims.given_name

Finalizar seu aplicativo Web

  1. No editor de código, abra app.js arquivo e adicione o código de app.js a ele.

  2. No editor de código, abra server.js arquivo e adicione o código de server.js a ele.

  3. No editor de código, abra package.json arquivo e atualize a scripts propriedade para:

    "scripts": {
    "start": "node server.js"
    }
    

Executar e testar o aplicativo Web Node/Express.js

Neste ponto, você pode testar seu aplicativo web em Node.js.

  1. Use as etapas em Criar um novo usuário para criar um usuário de teste no locatário da força de trabalho. Se você não tiver acesso ao locatário, peça ao administrador do seu locatário para criar o usuário para você.

  2. Para iniciar o servidor, execute os seguintes comandos de dentro do diretório do projeto:

    npm start
    
  3. Abra o navegador e vá para http://localhost:3000. Você deverá ver a página semelhante à seguinte captura de tela:

    Captura de tela de entrada em um aplicativo Web do Node.

  4. Selecione Entrar para iniciar o processo de entrada. Na primeira vez que entrar, você será solicitado a fornecer seu consentimento para permitir que o aplicativo entre e acesse seu perfil, conforme mostrado na seguinte captura de tela:

    Captura de tela exibindo a tela de consentimento do usuário

Depois de entrar com êxito, você será redirecionado de volta para a página inicial do aplicativo.

Próxima etapa