基于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会自动处理令牌的静默刷新逻辑:
- 当Access Token过期时,
acquireTokenSilent会自动使用Refresh Token获取新的Access Token。 - 如果Refresh Token也过期,MSAL会抛出
InteractionRequiredAuthError,此时前端需要触发交互式登录(如loginRedirect)。
后端无需手动处理Refresh Token,只需在令牌验证失败时返回401状态码,前端捕获后触发刷新逻辑即可(如前面Axios拦截器中的示例)。
内容的提问来源于stack exchange,提问作者Sandesh Rathnayake
相关产品推荐
相关产品推荐

