Token 與撤銷
流程跑完之後,串接方需要知道的 Signet token 事項:生命週期、刷新、撤銷、即時驗證。
On-Behalf-Of(OBO)
啟用後,機密用戶端 API A 可以將 Signet 簽發、aud 指向 A 的使用者 access token,交換成只能存取 B 的 token。
以表單 POST /oauth/token,提供 grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer、requested_token_use=on_behalf_of、assertion、單一 resource 與 scope。
A 使用 Basic 或表單憑證驗證,不能混用;重複/未知參數與 extra_claims 均拒絕。
管理員設定 OBO_ENABLED=true 與 OBO_POLICIES_FILE,明確指定 A 的 audience 所有權與 scope 對應,並將 B 加入 A 的允許資源。
使用者須先透過既有授權流程,同意 F 存取 A,以及 A 存取 B。缺少同意回傳 invalid_grant。
輸出保留使用者 sub,設定 client_id=A、act.sub=client:<A ID>、aud=B。
有效期取 OBO_TOKEN_EXPIRATION(預設及上限 5m)、client profile 與來源到期時間的最早者;不發 refresh/ID token,不允許再次 OBO。
所有 grant 的 act、may_act 均為服務端保留欄位。不包含 Agent OBO 或 Entra 串接。
線上驗證即時檢查來源、兩筆同意、使用者、clients 與政策。需要即時撤銷時,須結合本地 JWT 驗證與不快取的 introspection,並配置足夠限流額度。
啟用 ownership 檢查時,B 可能只取得 active,仍須自行檢查 audience 與 claims。離線驗證只會在 token 到期後失效。
關閉 OBO 會拒絕交換及委派 token 線上驗證;降版前須撤銷剩餘委派紀錄並等待最長 token 有效期。
使用場景、政策設定、兩次同意流程與完整交換範例:OBO 串接指南。
Token 生命週期
流程成功後您會拿到其中一或多種:
| Token | 格式 | 生命週期(依客戶端 profile 而定) | 用途 |
|---|---|---|---|
| Access token | JWT | short 15m · standard 10h · long 24h(近似值) |
Authorization: Bearer 打 API |
| Refresh token | JWT(請當不透明處理) | short 1d · standard 7d · long 30d(近似值) |
到 /oauth/token 換新 access token |
| ID token | JWT | 與 access token 相同 | 客戶端身分資訊 — 見 OIDC |
Refresh token 內部是 JWT,但您應該 把它當成不透明 — 在客戶端不要去解析其 claim,收穫為零還會耦合到內部實作。
實際數值取決於管理員為此客戶端選擇的 token profile。請一律相信 token response 的 expires_in,永遠不要寫死。
Audience Binding (aud claim)
當流程帶有 resource=<URL> 參數(RFC 8707),簽發的 access token 的 aud 會綁到該 resource。不帶 resource 時 aud 會回退到部署層級的 JWT_AUDIENCE。Refresh token 一律用靜態 JWT_AUDIENCE,不會帶每次請求的 resource。Resource server 應檢查 aud 等於自己的識別字,同時要求 type=access — 見 JWT 驗證 §Audience Binding。
每客戶端允許清單(預設全部拒絕):客戶端只能把
aud綁到管理員已加入該客戶端 允許資源清單 的resource值。若清單為空,任何 您傳入的resource=都會以invalid_target被拒 — 完全不傳resource(走JWT_AUDIENCE回退)仍可運作。請管理員把您客戶端需要的每個資源識別字加入允許清單。見 錯誤處理 §Resource Indicator 錯誤。
刷新 token
在 refresh token 本身過期之前,任何時刻都可以:
curl -X POST https://your-signet/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=REFRESH_TOKEN" \
-d "client_id=YOUR_CLIENT_ID"
# 機密客戶端:改用 -u "$CLIENT_ID:$CLIENT_SECRET",body 不要帶 client_id
回應 與初次 token 交換相同格式。
刷新時縮小
resource(RFC 8707 §2.2):可選擇帶resource=...來簽發aud為原授權 子集 的新 access token。要求未在原授權的 resource(擴張)會回400 invalid_target— refresh token 不會被消耗。省略resource則拿到綁定完整授權集的 token。
何時刷新:提前刷,例如過期前 30–60 秒,不要等到收到 401 才做。這樣可以避免請求失敗的中途錯誤與重試的噪音。
若您的部署啟用 rotation 模式(下節),還必須 將同一 session 的並發刷新序列化 — 兩個分頁同時刷新會直接毀掉 session。
輪轉模式:重用偵測的陷阱
某些 Signet 部署會啟用 rotation 模式(ENABLE_TOKEN_ROTATION=true)。在這個模式:
- 每次刷新會簽發 新的 refresh token 並 作廢 舊的。
- 若舊 refresh token 被再次使用(兩個分頁搶刷、網路抖動後重試、token 被偷去用),Signet 會偵測到重用,然後 把整個 token family 撤銷。
- 後續請求會回
{"error": "invalid_grant"}。
對串接方的實務意義:
- 序列化每個使用者 / session 的刷新(mutex、single-flight)。兩個分頁同時刷新,兩邊都拿著同一份舊 refresh token,一個會贏,另一個會用 剛被作廢 的舊 token 觸發重用偵測,整個 session 就死了。
- 立刻持久化新 refresh token。儲存更新前,不要先用舊的再發一輪請求。
- 刷新時收到
invalid_grant是終態 — 請顯示登入頁面,不要重試。
從 token response 本身無法判斷 rotation 是否開啟。若您的串接必須同時支援兩種模式,一律把回傳的 refresh_token 存下來(即使看起來一樣 — rotation 模式下它會不同)。
登出 — /oauth/revoke(RFC 7009)
登出時撤銷 refresh token(access token 可選),讓被偷走的也變成啞彈:
curl -X POST https://your-signet/oauth/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=REFRESH_TOKEN" \
-d "token_type_hint=refresh_token" \
-d "client_id=YOUR_CLIENT_ID"
# 機密客戶端:帶 client_secret 或使用 HTTP Basic
| 參數 | 必填 | 值 |
|---|---|---|
token |
是 | 要撤銷的 token |
token_type_hint |
否 | access_token 或 refresh_token |
client_id |
是 | 機密客戶端還需要 client_secret |
依 RFC 7009,不論 token 原本存不存在,端點一律回 200 OK。不要依賴回應判斷狀態 — 直接當作 token 已經消失。
撤銷一張 refresh token 也會(在 rotation 模式下)作廢整個 token family。撤銷 access token 不會 順便作廢對應的 refresh token — 要嘛兩者都撤,要嘛登出時撤 refresh token,短效的 access token 自然過期即可。
本端點同時也能撤銷個人 API 金鑰。 傳
token=sgk_...,不需要客戶端憑證 — 持有金鑰本身即是授權。適合讓 script 在工作結束時自行拆掉它的短命金鑰。見 API 金鑰。
Caller-Supplied Extra Claims
/oauth/token 接受一個選填的 extra_claims form 參數 — 一個 JSON 物件,內含要嵌入所簽發 token 的額外 claim。它在四種 grant 全都可用(authorization_code、device_code、client_credentials、refresh_token)。
curl -X POST https://your-signet/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-u "$CLIENT_ID:$CLIENT_SECRET" \
--data-urlencode 'extra_claims={"tenant":"acme","region":"eu"}'
規則與防護:
- 值必須是 JSON 物件。畸形 JSON、超過大小上限、或使用保留 key 都會回
400 invalid_request。
- 預設大小上限(營運者可各自調整或停用):≤ 4096 bytes raw、≤ 16 keys、每個值 ≤ 512 bytes。
- 保留 key 會被拒:標準 RFC/OIDC/Signet 管理的 claim(
iss、sub、aud、exp、iat、jti、type、scope、client_id、user_id、nonce、at_hash…)不能被覆寫。
- 整個功能可由營運者停用(
EXTRA_CLAIMS_ENABLED=false),此時任何非空的extra_claims都會被拒絕。
- 也會嵌入 refresh token。 對於會簽發 refresh token 的 grant(
authorization_code、device_code,以及 rotation 模式的refresh_token兌換),同樣的 claim 也會併入 refresh token JWT。您應把 refresh token 當成不透明,但它其實可以解碼 — 請把這些 claim 納入資料外洩評估,別把您不希望存在於一個數天有效 token 裡的東西放進extra_claims。
- 無狀態 — 不會被持久化。 Claim 不會隨授權一起儲存,所以您必須 每次刷新都重新提供
extra_claims才能讓它們留在新的 token 中。
信任模型:這些 claim 是 呼叫端自我宣稱的,並非由 Signet 背書。Resource server 必須把它們當成不可信輸入 — 絕不可把某個
extra_claims值當成 Signet 已為其擔保那樣拿來做授權判斷。
即時驗證
resource server 的本地 JWT 驗證見 JWT 驗證。那條路速度快、可水平擴展,但 無法 察覺被撤銷 / 停用的 token — 一張被撤銷的 JWT 在密碼學上仍然有效,直到 exp。
需要即時察覺撤銷時,用以下端點之一:
/oauth/introspect(RFC 7662)— 首選
需要客戶端認證(呼叫端本身必須是已註冊的 Signet 客戶端):
curl -X POST https://your-signet/oauth/introspect \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=TOKEN_TO_CHECK" \
-d "token_type_hint=access_token"
回應:
{
"active": true,
"scope": "openid profile email",
"client_id": "client-uuid",
"username": "alice",
"token_type": "Bearer",
"exp": 1700000000,
"iat": 1699996400,
"sub": "user-uuid",
"iss": "https://your-signet",
"jti": "unique-token-id"
}
若 token 無效、過期、被撤銷或停用,回應是:
{ "active": false }
擁有權關卡:預設情況下(
INTROSPECTION_REQUIRE_OWNERSHIP=true)上述完整 metadata 只 對您自己的客戶端所簽發的 token 回傳。若您內省一個屬於 其他 客戶端的 有效 token,回應會被精簡成{ "active": true }— 沒有sub、scope、username、client_id、aud、exp等。除非您的營運者已把此旗標設為false,否則不要把 resource server 建立在跨客戶端內省之上。
政策強制 要有即時性時用這個 — 管理儀表板、高價值操作,任何無法容忍「陳舊有效」窗口長達整個 access token 壽命(standard profile 約 10 小時、long 24 小時)的場景。
/oauth/tokeninfo — 輕量替代
以 Bearer header 帶 token,回傳較少的欄位。不用客戶端憑證(token 本身即認證):
curl -H "Authorization: Bearer TOKEN_TO_CHECK" https://your-signet/oauth/tokeninfo
{
"active": true,
"user_id": "user-uuid",
"client_id": "client-uuid",
"scope": "openid profile email",
"exp": 1700000000,
"iss": "https://your-signet",
"subject_type": "user"
}
Client Credentials 發出的 token,subject_type 會是 "client"。無效 token 回 401 並帶 OAuth invalid_token 錯誤。
個人 API 金鑰:本端點與
/oauth/introspect也接受不透明的sgk_金鑰,回應會多一個token_type: "personal_api_key",方便您套用不同政策。金鑰 不是 JWT,所以這兩個端點是驗證它的 唯一 途徑 — 見 API 金鑰。
已登入使用者可從 開發 → Token Info(/account/token-info)貼上任一憑證,查看經驗證的專案、過期時間、scopes 與主體資訊。表單使用 POST,不會把憑證放進網址或結果頁,且查詢會寫入使用者的稽核日誌。無效、過期、撤銷與未知憑證刻意共用相同結果。
該選哪個?
| 需求 | 方式 |
|---|---|
| resource server 大量驗證,可容忍短暫陳舊 | 本地 JWKS 驗證(不打 Signet) |
| 需要即時撤銷狀態,呼叫端能做客戶端認證 | /oauth/introspect |
| 使用者 session 內的互動式檢查 | 開發 → Token Info(/account/token-info) |
| 呼叫端本身就是此 token 的持有者 | /oauth/tokeninfo |
驗證不透明的 sgk_ 個人 API 金鑰 |
/oauth/tokeninfo,除非您就是以金鑰所綁定的那個應用認證 — 本地驗證不可能(API 金鑰) |
速率限制
Signet 對 token 路徑端點做每 IP 速率限制 — 唯一例外是 /oauth/introspect:每個已認證的客戶端應用各有自己的額度(同一個 egress IP 後面的整個 fleet 不會擠成一個桶),後面再加一道每 IP 上限。預設值(營運者可調整):
| 端點 | 預設限制 |
|---|---|
POST /oauth/token |
每 IP 20 req/min |
POST /oauth/device/code |
每 IP 10 req/min |
POST /device/verify |
每 IP 10 req/min |
POST /oauth/introspect |
每客戶端應用 600 req/min,每 IP 1200 |
GET /oauth/tokeninfo |
每 IP 600 req/min |
POST /account/token-info |
每 IP 600 req/min(獨立瀏覽器額度) |
GET/POST /oauth/userinfo |
每 IP 600 req/min |
POST /login |
每 IP 5 req/min |
超過限制回 429 Too Many Requests。若有 Retry-After header 請遵守;沒有的話指數退避。能批次就批次 — 可以本地 JWKS 驗證時,絕對不要每個請求都打 /oauth/tokeninfo 或 /oauth/introspect 來驗 JWT;唯一真的需要往返的憑證是 sgk_ 個人 API 金鑰,而且您應該短暫快取其驗證結果(API 金鑰)。