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

基于MERN栈的Azure AD B2C认证授权:MSAL选型困惑求助

MSAL for MERN + Azure AD B2C 选型与实现指导

选型明确:两者配合使用,而非二选一

  • MSAL React:负责前端(React)的用户交互认证流程,比如登录跳转/弹窗、存储用户凭证、获取访问令牌调用后端API、控制受保护页面的访问权限。
  • MSAL Node.js:负责后端(Node.js/Express)的令牌验证,确保前端传来的访问令牌是Azure AD B2C颁发的合法令牌,拦截未授权请求。

前端(React)实现步骤(MSAL React)

  1. 安装依赖:
npm install @azure/msal-react @azure/msal-browser
  1. 初始化MSAL实例(src/authConfig.js):
import { PublicClientApplication } from '@azure/msal-browser';

export const msalInstance = new PublicClientApplication({
  auth: {
    clientId: "YOUR_AZURE_B2C_CLIENT_ID",
    authority: "https://YOUR_TENANT.b2clogin.com/YOUR_TENANT.onmicrosoft.com/YOUR_SIGNUP_SIGNIN_POLICY",
    redirectUri: "http://localhost:3000",
    postLogoutRedirectUri: "http://localhost:3000"
  },
  cache: {
    cacheLocation: "sessionStorage",
    storeAuthStateInCookie: false
  }
});

export const loginRequest = {
  scopes: ["https://YOUR_TENANT.onmicrosoft.com/api/YOUR_API_SCOPE"]
};
  1. 用MSAL Provider包裹App组件(src/index.js):
import { MsalProvider } from '@azure/msal-react';
import { msalInstance } from './authConfig';

ReactDOM.render(
  <MsalProvider instance={msalInstance}>
    <App />
  </MsalProvider>,
  document.getElementById('root')
);
  1. 实现登录按钮与受保护组件:
import { useMsal, useIsAuthenticated } from '@azure/msal-react';
import { loginRequest } from './authConfig';

function LoginButton() {
  const { instance } = useMsal();
  const handleLogin = () => instance.loginRedirect(loginRequest);
  return <button onClick={handleLogin}>登录</button>;
}

function ProtectedContent() {
  const { instance } = useMsal();
  
  const getAccessToken = async () => {
    const activeAccount = instance.getActiveAccount();
    if (activeAccount) {
      const tokenResponse = await instance.acquireTokenSilent({ ...loginRequest, account: activeAccount });
      return tokenResponse.accessToken;
    }
    return null;
  };

  const callProtectedApi = async () => {
    const token = await getAccessToken();
    if (!token) return;
    const response = await fetch('http://localhost:5000/api/protected', {
      headers: { Authorization: `Bearer ${token}` }
    });
    const data = await response.json();
    console.log(data);
  };

  return (
    <div>
      <p>仅登录用户可见内容</p>
      <button onClick={callProtectedApi}>调用受保护API</button>
    </div>
  );
}

function App() {
  const isAuthenticated = useIsAuthenticated();
  return (
    <div className="App">
      {!isAuthenticated ? <LoginButton /> : <ProtectedContent />}
    </div>
  );
}

后端(Node.js/Express)实现步骤(MSAL Node.js)

  1. 安装依赖:
npm install @azure/msal-node express cors jsonwebtoken jwks-rsa
  1. 编写令牌验证中间件(middleware/authMiddleware.js):
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

const jwksClientInstance = jwksClient({
  jwksUri: 'https://YOUR_TENANT.b2clogin.com/YOUR_TENANT.onmicrosoft.com/YOUR_SIGNUP_SIGNIN_POLICY/discovery/v2.0/keys'
});

const getSigningKey = (header, callback) => {
  jwksClientInstance.getSigningKey(header.kid, (err, key) => {
    const signingKey = key.publicKey || key.rsaPublicKey;
    callback(null, signingKey);
  });
};

const validateAccessToken = (req, res, next) => {
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ message: '未提供有效访问令牌' });
  }

  const token = authHeader.split(' ')[1];
  jwt.verify(token, getSigningKey, {
    audience: "YOUR_BACKEND_API_CLIENT_ID",
    issuer: "https://YOUR_TENANT.b2clogin.com/YOUR_TENANT_ID/v2.0/",
    algorithms: ['RS256']
  }, (err, decoded) => {
    if (err) return res.status(403).json({ message: '令牌验证失败' });
    req.user = decoded;
    next();
  });
};

module.exports = validateAccessToken;
  1. 配置受保护API路由(routes/protected.js):
const express = require('express');
const router = express.Router();
const validateAccessToken = require('../middleware/authMiddleware');

router.get('/', validateAccessToken, (req, res) => {
  res.json({
    message: '成功访问受保护API',
    userInfo: {
      userId: req.user.sub,
      email: req.user.emails?.[0]
    }
  });
});

module.exports = router;
  1. 启动Express服务(server.js):
const express = require('express');
const cors = require('cors');
const protectedRoutes = require('./routes/protected');

const app = express();
app.use(cors());
app.use(express.json());

app.use('/api/protected', protectedRoutes);

const PORT = 5000;
app.listen(PORT, () => console.log(`后端服务运行在端口 ${PORT}`));

示例项目结构参考

mern-b2c-auth/
├── client/          # React前端
│   ├── src/
│   │   ├── authConfig.js
│   │   ├── index.js
│   │   └── App.js
│   └── package.json
├── server/          # Node.js后端
│   ├── middleware/
│   │   └── authMiddleware.js
│   ├── routes/
│   │   └── protected.js
│   ├── server.js
│   └── package.json
└── README.md

关键注意事项

  • 在Azure AD B2C中需注册两个应用:一个SPA类型的前端应用(启用授权码流),一个Web API类型的后端应用(定义权限范围),并为前端应用授予访问后端API的权限。
  • 替换代码中所有YOUR_*占位符为实际的Azure AD B2C租户ID、客户端ID、策略名称、API范围等信息。
  • 生产环境需启用HTTPS,前端缓存可根据需求切换为更安全的存储方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 08:36:19