Microsoft Graph Token Broker

统一代理 Microsoft Graph 的 OAuth 2.0 授权:调用方拿到可以直接访问 Microsoft 365 用户数据的 access token,而 client_secret 全程只留在 broker 里,不下发给任何调用方。

A single front door for Microsoft Graph OAuth 2.0. Callers get an access token for Microsoft 365 user data; the client secret never leaves the broker and is never handed to a caller.

登录管理回调域名白名单 →Sign in to manage redirect hosts → 用公司微软账号登录 Use your company Microsoft account
Broker URL
https://ssomanager.d.moatable.com
Client ID
87166ee3-a40a-44ac-b4e0-d40b1b4c3c6a
Tenant ID
9bad87a0-874b-4737-a37a-1178b7e6bd07

该用哪种方式Which flow should I use

场景方式新接入要改
没有浏览器——CLI、cron、无头脚本/device/codeAzure,一次性
broker 自用(个人脚本)/auth都不用改
第三方有自己的后端,能改 Azure AD/exchangeAzure,每接一个改一次
第三方有自己的后端,不能改 Azure ADclient_redirect本站自助申请
SituationFlowWhat a new caller changes
No browser — CLI, cron, headless script/device/codeAzure, once
Broker's own use (personal scripts)/authNothing
Third party has a backend and can edit Azure AD/exchangeAzure, once per caller
Third party has a backend but cannot edit Azure ADclient_redirectSelf-service here

Azure 侧的改动要有这个 App registration 的权限;client_redirect 的白名单以前要改 terraform 再 apply,现在在本站申请、由管理员审批即可。

Changes on the Azure side need permission on the App registration. The client_redirect whitelist used to mean editing terraform and running apply — now you request it here and an admin approves.

方式 D:client_redirect —— 不改 Azure AD,在本站自助申请Flow D: client_redirect — no Azure AD change, request it here

Azure AD 里始终只登记 broker 自己的 /callback,转发那一步由 broker 做:换完 token 后用一个 自动提交的 POST 表单把 token 送到你的地址。token 只出现在 POST body 里,不进 URL、浏览器历史或 Referer。

Azure AD only ever knows about the broker's own /callback; the broker does the forwarding itself. After exchanging the code it delivers the token to your URL through an auto-submitted POST form, so the token appears only in the POST body — never in a URL, browser history, or Referer.

  1. 登录本站,提交你要接收 token 的域名(如 app.example.com)。
  2. 管理员审批通过后最多 1 分钟生效。你随时可以在页面上看到并回收自己申请的域名。
  3. 把用户导向 broker 的 /auth,带上 client_redirect
  1. Sign in and submit the host that will receive the token (e.g. app.example.com).
  2. Once an admin approves it takes effect within a minute. You can see and withdraw your own hosts at any time.
  3. Send the user to the broker's /auth with client_redirect set.
GET https://ssomanager.d.moatable.com/auth
  ?client_redirect=https://app.example.com/oauth/receive
  &scope=openid profile email offline_access https://graph.microsoft.com/Mail.Read
  &state=<CSRF token>

登录完成后 broker 会 POST 表单到你的地址,字段为 access_token / refresh_token / expires_at / expires_in / token_type / state。不在白名单里的域名一律返回 400 client_redirect_not_allowed——这是防开放重定向,防止有人拼个链接把别人的 token 导流到自己网站。

After sign-in the broker POSTs a form to your URL with access_token, refresh_token, expires_at, expires_in, token_type and state. Any host not on the whitelist is rejected with 400 client_redirect_not_allowed — that is what stops someone from crafting a link that funnels another person's token to their own site.

其它三种方式The other three flows

A · Device Code(无浏览器)A · Device Code (no browser)

CLI、cron、无头服务器用。用户在任意设备上打开链接输入短代码,全程不需要回调地址。

For CLIs, cron jobs and headless servers. The user opens a link on any device and types a short code; no redirect URI involved.

要改的地方:Azure Portal → 这个应用 → Authentication → 打开 Allow public client flows。一次性,不按调用方计次。
What to change: Azure Portal → this app → Authentication → turn on Allow public client flows. Once, not per caller.
curl -X POST https://ssomanager.d.moatable.com/device/code
curl -X POST https://ssomanager.d.moatable.com/device/token \
  -H "Content-Type: application/json" \
  -d '{"device_code": "..."}'

B · Auth Code(broker 自用)B · Auth Code (broker's own use)

浏览器打开 /auth,登录后落到 /callback,直接在页面里返回 token JSON。

Open /auth in a browser; after sign-in you land on /callback, which returns the token JSON directly.

要改的地方:都不用改,broker 自己的回调早就注册好了。
What to change: nothing — the broker's own callback was registered when it was first set up.

C · /exchange(第三方用自己的 redirect_uri)C · /exchange (third party uses its own redirect_uri)

微软把 code 直接送到第三方自己的域名,第三方后端再服务器到服务器地把 code 交给 broker 换 token。

Microsoft sends the code straight to the third party's domain; their backend then exchanges it server-to-server through the broker.

要改的地方:Azure Portal → 这个应用 → Authentication → Add a platform → Web → 填第三方回调地址。每接一个新第三方改一次。
What to change: Azure Portal → this app → Authentication → Add a platform → Web → add their callback URL. Once per new third party.
curl -X POST https://ssomanager.d.moatable.com/exchange \
  -H "Content-Type: application/json" \
  -d '{"code": "...", "redirect_uri": "https://app.example.com/oauth/callback",
       "scope": "openid profile email offline_access https://graph.microsoft.com/Mail.Read",
       "client_name": "your-app"}'

Token 响应格式(四种方式通用)Token response (same for all four flows)

{
  "access_token": "...",
  "refresh_token": "...",
  "expires_at": "2026-04-29T10:00:00Z",
  "expires_in": 3600,
  "token_type": "Bearer"
}

过期前 5 分钟用 POST /refresh 续期(body 传 refresh_token;Device Code 拿到的 token 要额外传 public_client: true)。返回 401 reauth_required 表示背后的 MFA session 过期了,得按当初那种方式重新登录一次。

Renew with POST /refresh about 5 minutes before expiry (send refresh_token; tokens from Device Code also need public_client: true). A 401 reauth_required means the MFA session behind the refresh token expired and the user has to sign in again through whichever flow they originally used.