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

基于React+Express的Azure AD MSAL令牌传递、后端验证及刷新令牌实现方案咨询

实现Azure AD + MSAL + Express/Passport的角色认证与授权

首先得纠正一个关键细节:你当前前端存储的是idToken,但访问后端API应该使用Access Token——Id Token主要用于前端确认用户身份,Access Token才是后端用来验证用户权限、允许API访问的核心凭证。


一、前端需要传递给后端的数据

你需要从MSAL响应中获取Access Token,并在每个API请求的Authorization请求头中以Bearer <token>的格式传递给后端。

修改前端令牌获取逻辑

更新你的handAuth函数,改为存储Access Token:

const handAuth = (authenticated) => {
  console.log(authenticated);
  if (authenticated) {
    instance
      .acquireTokenSilent({
        ...loginRequest,
        account: accounts[0],
      })
      .then((response) => {
        // 存储Access Token而非Id Token
        window.sessionStorage.setItem('accessToken', response.accessToken);
        window.sessionStorage.setItem('email', response.account.username);
        window.sessionStorage.setItem('account', JSON.stringify(response.account));
      })
      .catch((error) => {
        // 静默获取失败时,触发交互式令牌获取
        if (error instanceof InteractionRequiredAuthError) {
          instance.acquireTokenRedirect(loginRequest);
        }
        console.error(error);
      });
  }
};

Axios请求自动携带令牌

为Axios添加请求拦截器,自动在API请求中注入令牌:

import axios from 'axios';

const apiClient = axios.create({
  baseURL: 'http://localhost:5000/api',
});

// 请求拦截器:自动添加Authorization头
apiClient.interceptors.request.use((config) => {
  const accessToken = window.sessionStorage.getItem('accessToken');
  if (accessToken) {
    config.headers.Authorization = `Bearer ${accessToken}`;
  }
  return config;
});

// 示例调用/api/users
apiClient.get('/users')
  .then(response => console.log(response.data))
  .catch(error => {
    // 捕获401令牌过期错误,触发刷新逻辑
    if (error.response?.status === 401) {
      const account = JSON.parse(window.sessionStorage.getItem('account'));
      instance.acquireTokenSilent({...loginRequest, account})
        .then(res => {
          window.sessionStorage.setItem('accessToken', res.accessToken);
          // 重试原请求
          return apiClient(error.config);
        })
        .catch(() => instance.loginRedirect());
    }
  });

二、后端用Passport.js验证Azure AD令牌

我们使用passport-azure-ad这个官方策略来验证Azure AD颁发的Access Token。

1. 安装依赖

npm install passport passport-azure-ad

2. 创建Passport配置文件

在项目中新建auth.js文件,配置Azure AD验证策略:

const passport = require('passport');
const { BearerStrategy } = require('passport-azure-ad');

// 从环境变量读取Azure AD配置(需提前在Azure门户获取)
const azureADConfig = {
  identityMetadata: `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/v2.0/.well-known/openid-configuration`,
  clientID: process.env.AZURE_CLIENT_ID, // 你的API应用的Client ID
  validateIssuer: true,
  issuer: `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/v2.0`,
  audience: process.env.AZURE_CLIENT_ID,
  loggingLevel: 'info',
  passReqToCallback: false,
};

// 初始化Bearer令牌验证策略
const bearerStrategy = new BearerStrategy(azureADConfig, (token, done) => {
  // 这里可以根据token中的声明(比如upn、roles)查询数据库获取用户详情
  // token参数包含了用户的所有身份信息,比如token.roles可用于角色授权
  return done(null, token, { scope: token.scope });
});

passport.use(bearerStrategy);

module.exports = passport;

3. 集成Passport到Express应用

修改你的后端主文件,启用Passport并保护API路由:

const http = require('http');
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
const HttpError = require('./models/http-error');
const passport = require('./auth'); // 引入Passport配置
require('dotenv').config();

const server = express();
const uri = process.env.ATLAS_URI;
const port = process.env.PORT || 5000;

server.use(cors());
server.use(express.json());
server.use(passport.initialize()); // 初始化Passport

// 保护/api/users路由:仅验证通过的请求可访问
const userRoutes = require('./routes/user-routes');
server.use('/api/users', 
  passport.authenticate('oauth-bearer', { session: false }), 
  userRoutes
);

//#region Error Handling
server.use((req, res, next) => {
  const error = new HttpError('Could not find this route.', 404);
  throw error;
});

server.use((error, req, res, next) => {
  if (res.headersSent) {
    return next(error);
  }
  res.status(error.code || 500);
  res.json({ message: error.message || 'An unknown error occurred!' });
});

mongoose.connect(uri)
  .then(() => {
    server.listen(port, () => {
      console.log(`Server running on port ${port}`);
    });
  })
  .catch(err => {
    console.error(err);
  });

4. 基于角色的授权(可选)

如果需要限制特定角色访问接口,可以在路由中添加角色检查:

// routes/user-routes.js
const express = require('express');
const router = express.Router();
const HttpError = require('../models/http-error');

router.get('/', (req, res, next) => {
  // 从req.user中获取用户角色(Azure AD令牌中的roles声明)
  const userRoles = req.user.roles;
  if (!userRoles?.includes('Admin')) {
    return next(new HttpError('You are not authorized to access this resource.', 403));
  }
  // 返回用户数据逻辑
  res.json({ users: [...] });
});

module.exports = router;

三、刷新令牌的处理

MSAL.js会自动处理令牌的静默刷新逻辑:

  1. 当Access Token过期时,acquireTokenSilent会自动使用Refresh Token获取新的Access Token。
  2. 如果Refresh Token也过期,MSAL会抛出InteractionRequiredAuthError,此时前端需要触发交互式登录(如loginRedirect)。

后端无需手动处理Refresh Token,只需在令牌验证失败时返回401状态码,前端捕获后触发刷新逻辑即可(如前面Axios拦截器中的示例)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 19:52:50