教程:在 Node.js Web 应用程序中添加添加登录和退出登录

本教程是一个系列教程的最后一部分,演示如何生成 Node.js Web 应用,并使用 Microsoft Entra 管理中心准备将其用于身份验证。 在本系列教程的第 2 部分中,你创建了一个 Node.js Web 应用并组织了所有必需的文件。 在本教程中,你将添加登录、注册和退出登录 Node.js Web 应用。 若要简化向 Node.js Web 应用添加身份验证的操作,请使用适用于 Node 的 Microsoft 身份验证库 (MSAL)。 登录流使用 OpenID Connect (OIDC) 身份验证协议,可安全地登录用户。

在本教程中,你将:

  • 添加登录和退出登录逻辑
  • 查看 ID 令牌声明
  • 运行应用并测试登录和退出登录体验。

先决条件

创建 MSAL 配置对象

在代码编辑器中,打开 authConfig.js 文件,然后添加以下代码:

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';

/**
 * 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
        authority: process.env.AUTHORITY || `https://${TENANT_SUBDOMAIN}.ciamlogin.com/`, // replace "Enter_the_Tenant_Subdomain_Here" with your tenant name
        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
};

msalConfig 对象包含一组用于自定义身份验证流行为的配置选项。

在 authConfig.js 文件中:

  • Enter_the_Application_Id_Here 替换为之前注册的应用的应用程序(客户端)ID。

  • 查找 Enter_the_Tenant_Subdomain_Here 并将其替换为目录(租户)子域。 例如,如果租户主域为 contoso.onmicrosoft.com,请使用 contoso。 如果没有租户名称,请了解如何读取租户详细信息

  • Enter_the_Client_Secret_Here 替换为之前复制的应用机密值。

如果使用 .env 文件存储配置信息,请执行以下操作:

  1. 在代码编辑器中,打开 .env 文件,然后添加以下代码。

        CLIENT_ID=Enter_the_Application_Id_Here
        TENANT_SUBDOMAIN=Enter_the_Tenant_Subdomain_Here
        CLIENT_SECRET=Enter_the_Client_Secret_Here
        REDIRECT_URI=http://localhost:3000/auth/redirect
        POST_LOGOUT_REDIRECT_URI=http://localhost:3000
    
  2. 替换 Enter_the_Application_Id_HereEnter_the_Tenant_Subdomain_HereEnter_the_Client_Secret_Here 占位符,如前所述。

导出 authConfig.js 文件中的 msalConfigREDIRECT_URITENANT_SUBDOMAINPOST_LOGOUT_REDIRECT_URI 变量,这样就可以在需要该文件的任何位置访问这些变量。

使用自定义 URL 域(可选)

使用自定义域可完全标记身份验证 URL。 从用户的角度来看,用户在身份验证过程中仍留在你的域中,而不是重定向到 ciamlogin.com 域名

通过以下步骤使用自定义域:

  1. 使用为外部租户中的应用启用自定义 URL 域中的步骤为外部租户启用自定义 URL 域。

  2. 在 authConfig.js 文件中,找到 auth 对象,然后执行以下操作

    1. authority 属性的值更新为 https://Enter_the_Custom_Domain_Here/Enter_the_Tenant_ID_Here。 将 Enter_the_Custom_Domain_Here 替换为你的自定义 URL 域,并将 Enter_the_Tenant_ID_Here 替换为你的租户 ID。 如果没有租户 ID,请了解如何读取租户详细信息
    2. 添加值为 [Enter_the_Custom_Domain_Here] 的 knownAuthorities 属性

对 authConfig.js 文件进行更改后,如果自定义 URL 域为 login.contoso.com 且租户 ID 为 aaaabbbb-0000-cccc-1111-dddd2222eeee,则文件内容应类似于以下代码片段

//...
const msalConfig = {
    auth: {
        authority: process.env.AUTHORITY || 'https://login.contoso.com/aaaabbbb-0000-cccc-1111-dddd2222eeee', 
        knownAuthorities: ["login.contoso.com"],
        //Other properties
    },
    //...
};

添加快速路由

快速路由了提供支持执行操作(例如登录、退出登录和查看 ID 令牌声明)的终结点。

应用入口点

在代码编辑器中,打开 routes/index.js 文件,然后添加以下代码:

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;

/ 路由是应用程序的入口点。 它呈现你之前在生成应用 UI 组件中创建的 views/index.hbs 视图。 isAuthenticated 是一个布尔变量,用于确定在视图中看到的内容。

登录和注销

  1. 在代码编辑器中,打开 routes/auth.js 文件,然后将来自 auth.js 的代码添加到其中。

  2. 在代码编辑器中,打开 controller/authController.js 文件,然后将来自 authController.js 的代码添加到其中。

  3. 在代码编辑器中,打开 auth/AuthProvider.js 文件,然后将来自 AuthProvider.js 的代码添加到其中。

    /signin/signout/redirect 路由在 routes/auth.js 文件中定义,但在 auth/AuthProvider.js 类别中实现它们的逻辑。

  • login 方法处理 /signin 路由:

    • 它通过触发授权代码流的第一个分支来启动登录流。

    • 它使用你之前创建的 MSAL 配置对象 msalConfig 初始化机密客户端应用程序实例。

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

      getMsalInstance 方法定义如下:

          getMsalInstance(msalConfig) {
              return new msal.ConfidentialClientApplication(msalConfig);
          }
      
    • 授权代码流的第一个分支会生成授权代码请求 URL,然后重定向到该 URL 以获取授权代码。 第一个分支在 redirectToAuthCodeUrl 方法中实现。 注意我们如何使用 MSAL 的 getAuthCodeUrl 方法生成授权代码 URL:

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

      然后重定向到授权代码 URL 本身。

      //...
      res.redirect(authCodeUrlResponse);
      //...
      
  • handleRedirect 方法处理 /redirect 路由:

    • 你之前已按照注册 Web 应用中的说明在 Microsoft Entra 管理中心将此 URL 设置为 Web 应用的重定向 URI。

    • 此终结点实现授权代码流的第二个分支。 它使用授权代码通过 MSAL 的 acquireTokenByCode 方法请求 ID 令牌。

      //...
      const tokenResponse = await msalInstance.acquireTokenByCode(authCodeRequest, req.body);
      //...
      
    • 收到响应后,你可以创建一个 Express 会话并在其中存储想要的任何信息。 你需要将 isAuthenticated 包括在内并将其设置为 true

      //...        
      req.session.idToken = tokenResponse.idToken;
      req.session.account = tokenResponse.account;
      req.session.isAuthenticated = true;
      //...
      
  • logout 方法处理 /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);
        });
    }
    
    • 它发起退出登录请求。

    • 如果想要将用户从应用程序中退出登录,只是终止用户的会话是不够的。 必须将用户重定向到 logoutUri。 否则,用户可能可以向应用程序重新进行身份验证,且无需重新输入其凭据。 如果租户的名称为 contoso,则 logoutUri 类似于 https://contoso.ciamlogin.com/contoso.onmicrosoft.com/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000

查看 ID 令牌声明

在代码编辑器中,打开 routes/users.js 文件,然后添加以下代码:

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;

如果用户已经过身份验证,则 /id 路由使用 views/id.hbs 视图显示 ID 令牌声明。 你之前在生成应用 UI 组件中添加了此视图。

提取特定的 ID 令牌声明,例如“给定名称”:

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

完成 Web 应用

  1. 在代码编辑器中,打开 app.js 文件,然后将来自 app.js 的代码添加到其中。

  2. 在代码编辑器中,打开 server.js 文件,然后将来自 server.js 的代码添加到其中。

  3. 在代码编辑器中,打开 package.json 文件,然后将 scripts 属性更新为:

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

运行并测试 Web 应用

  1. 在终端中,确保你位于包含 Web 应用(例如 ciam-sign-in-node-express-web-app)的项目文件夹中。

  2. 在终端中,运行以下命令:

    npm start
    
  3. 打开浏览器,然后转到 http://localhost:3000。 应会看到类似于以下屏幕截图的页面:

    屏幕截图显示登录到 Node Web 应用。

  4. 在页面完成加载后,选择“登录”链接。 系统会提示你进行登录。

  5. 在登录页上,键入你的“电子邮件地址”,选择“下一步”,键入你的“密码”,然后选择“登录”。 如果没有帐户,请选择“无帐户? 创建一个”链接,以启动注册流。

  6. 如果选择注册选项,则在填写电子邮件、一次性密码、新密码和更多帐户详细信息后,你就完成了整个注册流程。 你会看到类似于以下屏幕截图的页面。 如果选择登录选项,则会看到类似的页面。

    屏幕截图显示了查看 ID 令牌声明。

  7. 选择“退出登录”以让用户从 Web 应用退出登录,或选择“查看 ID 令牌声明”以查看所有 ID 令牌声明。

另请参阅