如何在Node <=13的ExpressJS应用中实现Microsoft Teams登录与通知
实现方案概述
完全可以通过Microsoft Graph API在ExpressJS后端实现需求,无需Teams Bots/Apps,以下是分步指引、兼容Node13的工具选择及代码示例。
一、Entra Admin中心应用注册配置
- 注册Web应用,重定向URI设为你的Express登录回调地址(如
http://localhost:3000/auth/microsoft/callback) - 添加Delegated权限并按需申请管理员同意:
User.Read:获取用户基本信息(登录必备)Group.Read.All:读取用户加入的Teams团队(Teams对应Office 365组)Channel.ReadBasic.All:读取团队下的频道列表ChannelMessage.Send:向指定频道发送消息- 若需通过API创建Incoming Webhook,需添加
ChannelSettings.ReadWrite.All
- 生成客户端密码(Client Secret),保存好客户端ID、租户ID、客户端密码
二、兼容Node13的依赖选择
- 身份认证:
@azure/msal-node@1.14.0(v1.x版本支持Node10+,v2+要求Node14+) - Graph API调用:
@microsoft/microsoft-graph-client@3.0.0(v3.x支持Node10+,v4+要求Node14+) - Express中间件:
express-session(用于存储用户会话和授权状态)
安装命令:
npm install @azure/msal-node@1.14.0 @microsoft/microsoft-graph-client@3.0.0 express-session express
三、核心流程与代码实现
1. 用户登录(OAuth2授权码流程)
const express = require('express'); const session = require('express-session'); const { ConfidentialClientApplication } = require('@azure/msal-node'); const app = express(); app.use(session({ secret: 'your-session-secret', resave: false, saveUninitialized: false })); // MSAL配置 const msalConfig = { auth: { clientId: 'YOUR_CLIENT_ID', authority: 'https://login.microsoftonline.com/YOUR_TENANT_ID', clientSecret: 'YOUR_CLIENT_SECRET' } }; const cca = new ConfidentialClientApplication(msalConfig); // 登录路由:跳转至Microsoft授权页 app.get('/auth/microsoft', (req, res) => { const authCodeUrlParameters = { scopes: ['User.Read', 'Group.Read.All', 'Channel.ReadBasic.All', 'ChannelMessage.Send'], redirectUri: 'http://localhost:3000/auth/microsoft/callback' }; cca.getAuthCodeUrl(authCodeUrlParameters) .then((response) => res.redirect(response)) .catch((error) => res.status(500).send(error.message)); }); // 回调路由:获取授权码并交换token app.get('/auth/microsoft/callback', (req, res) => { const tokenRequest = { code: req.query.code, scopes: ['User.Read', 'Group.Read.All', 'Channel.ReadBasic.All', 'ChannelMessage.Send'], redirectUri: 'http://localhost:3000/auth/microsoft/callback' }; cca.acquireTokenByCode(tokenRequest) .then((response) => { // 存储token到会话 req.session.accessToken = response.accessToken; req.session.userId = response.account.localAccountId; res.redirect('/select-channel'); }) .catch((error) => res.status(500).send(error.message)); });
2. 获取用户Teams频道列表
const { Client } = require('@microsoft/microsoft-graph-client'); // 初始化Graph客户端 function getGraphClient(accessToken) { const authProvider = { getAccessToken: async () => accessToken }; return Client.initWithMiddleware({ authProvider }); } // 获取用户加入的Teams及频道 app.get('/select-channel', async (req, res) => { if (!req.session.accessToken) return res.redirect('/auth/microsoft'); const client = getGraphClient(req.session.accessToken); try { // 获取用户加入的Teams团队 const teams = await client.api('/me/joinedTeams').get(); // 为每个团队获取频道列表 const teamsWithChannels = await Promise.all(teams.value.map(async (team) => { const channels = await client.api(`/teams/${team.id}/channels`).get(); return { id: team.id, displayName: team.displayName, channels: channels.value.filter(ch => ch.membershipType === 'standard') // 过滤标准频道 }; })); // 渲染频道选择页面(这里简化为返回JSON,实际可渲染模板) res.json(teamsWithChannels); } catch (error) { res.status(500).send(error.message); } });
3. 后端发起频道通知
方式一:通过Graph API发送消息
app.post('/send-notification', async (req, res) => { const { channelId, message } = req.body; if (!req.session.accessToken || !channelId || !message) return res.status(400).send('参数缺失'); const client = getGraphClient(req.session.accessToken); try { await client.api(`/channels/${channelId}/messages`).post({ body: { content: message } }); res.send('通知发送成功'); } catch (error) { res.status(500).send(error.message); } });
方式二:使用Incoming Webhook(无需持续token)
如果不想依赖用户token有效期,可让用户选择频道后创建/提供Incoming Webhook URL:
- 通过API创建Webhook(需
ChannelSettings.ReadWrite.All权限):
async function createIncomingWebhook(client, teamId, channelId, webhookName) { const webhook = await client.api(`/teams/${teamId}/channels/${channelId}/webhooks`).post({ displayName: webhookName, configuration: { url: '' // 由Graph自动生成URL } }); return webhook.configuration.url; }
- 发送通知时直接POST到Webhook URL:
const axios = require('axios'); // axios@0.27.x支持Node13 app.post('/send-webhook-notification', async (req, res) => { const { webhookUrl, message } = req.body; try { await axios.post(webhookUrl, { text: message }); res.send('通知发送成功'); } catch (error) { res.status(500).send(error.message); } });
四、关键注意事项
- Node13兼容性:所有依赖必须锁定到支持Node13的版本,避免安装最新版
- 权限范围:
Group.Read.All和ChannelSettings.ReadWrite.All需要租户管理员同意,若无法获取管理员同意,可引导用户手动添加Incoming Webhook(在Teams频道设置中添加"Incoming Webhook"连接器,复制URL给后端) - Token有效期:Delegated token有效期约1小时,需实现刷新token逻辑(msal-node可自动处理刷新)
- 会话存储:生产环境需用Redis等持久化存储替代默认的内存session
内容的提问来源于stack exchange,提问作者Wolf Dev
相关产品推荐
相关产品推荐

