You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在非单体Node.js应用中拆分msal-node的全局配置与中间件?

拆分Node.js Azure AD B2C(msal-node)应用的配置与模块方案

建议的目录结构

your-app/
├── config/
│   ├── msal.js          # 集中存放msal配置与实例
├── middleware/
│   ├── authMiddleware.js # 认证校验类中间件
├── routes/
│   ├── authRoutes.js    # 登录/回调/登出等认证路由
│   ├── protectedRoutes.js # 需要认证的业务路由
├── index.js             # Express应用入口

1. 迁移msal配置与实例到独立模块

Node.js模块系统默认是单例模式,同一模块多次导入只会返回首次创建的实例,刚好解决你担心的ConfidentialClientApplication重复实例问题。在config/msal.js中集中管理所有msal相关配置:

const msal = require('@azure/msal-node');

// 1. 定义confidential client核心配置
const confidentialClientConfig = {
    auth: {
        clientId: 'YOUR_CLIENT_ID',
        authority: 'https://YOUR_TENANT.b2clogin.com/YOUR_TENANT.onmicrosoft.com/YOUR_USER_FLOW',
        clientSecret: 'YOUR_CLIENT_SECRET'
    },
    cache: {
        cacheLocation: 'memory',
        storeAuthStateInCookie: true
    }
};

// 2. 创建全局唯一的confidential client实例
const confidentialClientApplication = new msal.ConfidentialClientApplication(confidentialClientConfig);

// 3. 其他msal请求参数(按你的8项需求补充)
const authCodeRequest = {
    redirectUri: 'http://localhost:3000/auth/callback',
    scopes: ['openid', 'profile', 'offline_access']
};

const tokenRequest = {
    redirectUri: 'http://localhost:3000/auth/callback',
    scopes: ['openid', 'profile']
};

// 导出所有需要的配置与实例
module.exports = {
    confidentialClientConfig,
    confidentialClientApplication,
    authCodeRequest,
    tokenRequest
    // 剩余配置项依次添加即可
};

2. 在中间件与路由中访问msal资源

直接通过模块导入即可获取同一实例,无需额外处理全局变量:

示例:认证中间件(middleware/authMiddleware.js)

const { confidentialClientApplication, tokenRequest } = require('../config/msal');

// 校验用户是否已登录的中间件
const isAuthenticated = async (req, res, next) => {
    const account = req.session.account;
    if (!account) return res.redirect('/auth/login');

    // 静默刷新过期令牌
    try {
        const silentRequest = {
            account: account,
            ...tokenRequest
        };
        const response = await confidentialClientApplication.acquireTokenSilent(silentRequest);
        req.session.accessToken = response.accessToken;
        next();
    } catch (err) {
        res.redirect('/auth/login');
    }
};

module.exports = { isAuthenticated };

示例:认证路由(routes/authRoutes.js)

const express = require('express');
const router = express.Router();
const { confidentialClientApplication, authCodeRequest } = require('../config/msal');

// 登录跳转路由
router.get('/login', (req, res) => {
    const authParams = {
        ...authCodeRequest,
        state: 'CUSTOM_CSRF_STATE' // 自定义防CSRF状态值
    };
    const authUrl = confidentialClientApplication.getAuthCodeUrl(authParams);
    res.redirect(authUrl);
});

// 认证回调路由
router.get('/callback', async (req, res) => {
    const tokenParams = {
        ...authCodeRequest,
        code: req.query.code,
        state: req.query.state
    };

    try {
        const response = await confidentialClientApplication.acquireTokenByCode(tokenParams);
        req.session.account = response.account;
        req.session.accessToken = response.accessToken;
        res.redirect('/protected');
    } catch (err) {
        res.status(500).send(`认证失败:${err.message}`);
    }
});

// 登出路由
router.get('/logout', (req, res) => {
    const logoutUrl = `https://YOUR_TENANT.b2clogin.com/YOUR_TENANT.onmicrosoft.com/YOUR_USER_FLOW/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000`;
    req.session.destroy();
    res.redirect(logoutUrl);
});

module.exports = router;

示例:受保护路由(routes/protectedRoutes.js)

const express = require('express');
const router = express.Router();
const { isAuthenticated } = require('../middleware/authMiddleware');

// 需登录才能访问的页面
router.get('/', isAuthenticated, (req, res) => {
    res.send(`欢迎访问受保护页面,当前令牌:${req.session.accessToken}`);
});

module.exports = router;

3. 在入口文件整合所有模块

const express = require('express');
const session = require('express-session');
const authRoutes = require('./routes/authRoutes');
const protectedRoutes = require('./routes/protectedRoutes');

const app = express();

// 配置session用于存储用户认证信息
app.use(session({
    secret: 'YOUR_SESSION_SECRET',
    resave: false,
    saveUninitialized: false,
    cookie: { secure: false } // 生产环境建议开启HTTPS并设为true
}));

// 挂载路由
app.use('/auth', authRoutes);
app.use('/protected', protectedRoutes);

// 首页
app.get('/', (req, res) => {
    res.send('<a href="/auth/login">点击登录</a>');
});

app.listen(3000, () => {
    console.log('服务器运行在 http://localhost:3000');
});

核心优势说明

  • 单例实例保障:Node.js模块的单例特性确保ConfidentialClientApplication只会被初始化一次,避免资源浪费与不一致问题。
  • 职责清晰分离:配置、中间件、路由各自独立维护,代码结构更易读、易扩展。
  • 集中配置管理:所有msal相关参数都在config/msal.js中,后续修改无需遍历多个文件。

内容的提问来源于stack exchange,提问作者user1063287

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 21:05:25