统一代理 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| 场景 | 方式 | 新接入要改 |
|---|---|---|
| 没有浏览器——CLI、cron、无头脚本 | /device/code | Azure,一次性 |
| broker 自用(个人脚本) | /auth | 都不用改 |
| 第三方有自己的后端,能改 Azure AD | /exchange | Azure,每接一个改一次 |
| 第三方有自己的后端,不能改 Azure AD | client_redirect | 本站自助申请 |
| Situation | Flow | What a new caller changes |
|---|---|---|
| No browser — CLI, cron, headless script | /device/code | Azure, once |
| Broker's own use (personal scripts) | /auth | Nothing |
| Third party has a backend and can edit Azure AD | /exchange | Azure, once per caller |
| Third party has a backend but cannot edit Azure AD | client_redirect | Self-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.
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.
app.example.com)。/auth,带上 client_redirect。app.example.com)./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.
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.
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": "..."}'
浏览器打开 /auth,登录后落到 /callback,直接在页面里返回 token JSON。
Open /auth in a browser; after sign-in you land on /callback, which returns the token JSON directly.
微软把 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.
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"}'
{
"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.