機器流程(client_credentials)
背景同步、批次匯入、伺服器對伺服器的整合走這條。主體是 service account,不是某個使用者。
現況:
client_credentials尚未在/oauth2/token實作,discovery 目前也還沒有公告這個 grant。下面是已定案的契約,可以照著開發並對 mock server 驗證,但還不能對正式環境呼叫。
取 token
POST https://auth.lightup.tech/oauth2/token,application/x-www-form-urlencoded。
| 欄位 | 必填 | 說明 |
|---|---|---|
grant_type |
必填 | 固定 client_credentials |
client_id |
必填 | 第三方 client id,形狀 tp_<tenant label>_<slug>,由 amygdala 配發。 |
client_secret |
選填 | service account 的機器憑證。用 client_secret_basic 時省略。 不要放進版本控制、前端 bundle 或 log。 |
scope |
選填 | 以空白分隔的能力清單(OAuth 慣例,不是逗號)。 實際拿到的 scope 是各層交集後的結果,可能比請求的少—— 以回應的 scope 欄位為準,不要假設拿到的就是送出去的。 |
回應
| 欄位 | 必填 | 說明 |
|---|---|---|
access_token |
必填 | profile v2 access token(JWS,header typ: at+jwt、alg: ES256)。 當成不透明字串使用:要知道自己是誰請呼叫 GET /v1/me, 不要在 client 自行解析 claims 做授權判斷。 |
token_type |
必填 | 固定 Bearer 一律 Bearer。放進 Authorization: Bearer <access_token>。 |
expires_in |
必填 | 剩餘秒數。v1 是 900(15 分)。不要寫死這個數字—— 照回應的值算到期時間。 |
scope |
必填 | 實際生效的能力,已是請求 ∩ grant ∩ 方案上限的結果。 可能少於你請求的;以這裡為準。 |
refresh_token |
選填 | 只有 authorization_code 與 refresh_token 會回。 client_credentials 不回——機器到期直接再換一張。 每次續期都會換新的,舊的立即失效。 |
{
"access_token": "<ES256 at+jwt>",
"token_type": "Bearer",
"expires_in": 900,
"scope": "lightup.person.read lightup.person.metadata.write"
}
- TTL 由
expires_in決定,沒有 refresh token。 到期就重新換一張——機器隨時可以再換。不要把 900 寫死。 - 實際 scope = 請求的 scope ∩ service account 的
grantedCapabilities∩ 租戶方案上限。任何一層縮水,token 就縮水。 - 交集為空 →
invalid_scope,平台不會發一張沒有 scope 的 token。 - 回應的
scope是實際生效的集合,可能比你請求的少。要以它為準。 - 回應帶
Cache-Control: no-store:token 不得被任何中介快取。
錯誤
錯誤狀態:400、401、429。
body 是 OAuth 既有的 TokenError 形狀(error + error_description),
不是本 API 其他端點的 Problem——不要把兩者放進同一個解析路徑。
error |
意思 |
|---|---|
invalid_client |
client id/secret 不對,或 client 已停用 |
invalid_grant |
code/refresh token 無效、過期或已用過;PKCE code_verifier 不符 |
invalid_scope |
請求的 scope 不在 client 允許範圍、tenant grant 或方案上限內 |
unsupported_grant_type |
這個 grant 沒開。client_credentials 今天就是這個答案 |
client_credentials 是安全可重試的(不像 authorization_code 與 refresh_token 是一次性的)。
v1 的三種 grant
grant_type |
必填欄位 |
|---|---|
client_credentials |
grant_type、client_id |
authorization_code |
grant_type、client_id、code、code_verifier、redirect_uri |
refresh_token |
grant_type、client_id、refresh_token |
用 token
GET /v1/me HTTP/1.1
Host: api.lightup.tech
Authorization: Bearer <access_token>
workload token 呼叫 GET /v1/me,回答的是 service account 自己:
{
"subjectType": "service_account",
"subjectId": "acc_01j8m4k2r7e9v3xq5w8n0tzc8d",
"tenantId": "ten_0cqqg39h2199x8fd5kf58s0eb5",
"clientId": "tp_example_sync",
"scopes": ["lightup.person.read", "lightup.person.metadata.write"],
"actor": { "accountId": "acc_01j8m4k2r7e9v3xq5w8n0tzc8d" }
}
機器 token 不會「變成某個使用者」。 機器可以做獲准的組織級同步,但不能聲稱平台已經驗證過某一位終端使用者。如果你的後端用一張機器 token 讀一群人的資料,你的使用者隔離是你的責任,平台不負責。
快取與重取
- 在 token 有效期內重複使用同一張,不要每個請求都換一張。
- 快取以「剩餘秒數」為準,並預留緩衝(例如剩 60 秒就換)。
- 拿到 401 時換一張新 token 再試一次;同一張 token 重試永遠不會變成成功。
- 連續 401:檢查 service account 是否被停用、憑證是否已輪替。不要無限重試。
輪替期間
兩把 secret 並存最長 7 天。輪替時的安全做法:
- 平台建立
nextsecret 並交付給你。 - 你先把
next部署到設定,確認可以換到 token。 - 通知平台 promote,
current立刻失效。 - 如果第 2 步失敗,舊的還在,可以退回——這就是兩把並存的用意。
不要做的事
- 不要把 service account secret 放進行動 App、桌面 App 或瀏覽器。那是 public client 的情境,走使用者流程。
- 不要用多個 service account 繞過限流。限流有 tenant 與 account 兩層,繞到 tenant 層還是會撞。
- 不要在互動操作失敗時改用機器 token 完成同一件事。兩種授權必須分開可辨認。