メインコンテンツまでスキップ

リソース単位のアクセストークン

このドキュメントの目的​

アクセストークンをAPI単位で絞り込み、リソースサーバーが自分宛てのトークンだけを受け入れられるようにすることが目標です。

学べること​

✅ リソース識別子の基礎

  • audがリソースサーバーを指す理由
  • スコープとリソースの対応づけ

✅ 実践的な知識

  • Management APIでの設定方法
  • 段階的な導入と差し戻し

所要時間​

⏱️ 約10分

前提条件​

  • how-to-02でテナント作成完了
  • アクセストークンをJWT形式で発行していること(access_token_type: "JWT")

なぜ必要か​

JWTアクセストークンをリソースサーバーが自前で検証する場合、署名を確かめるだけでは足りません。そのトークンが自分宛てかを確認する必要があります。

複数のAPIを持つサービスで、この確認が無いとこうなります。

 決済API向けに発行したトークンを、クライアントが管理APIに提示
→ 署名は正しい(同じ認可サーバーが発行したもの)
→ 管理APIが受け入れてしまう

audクレームはリソースサーバーの識別子を運びます。管理APIが「audが自分の識別子でなければ拒否する」を実装できて初めて、上の経路が塞がります。

注記

audに入るのはリソースサーバーであってクライアントではありません。クライアントはclient_idクレームが表します。IDトークンのaudがクライアントを指すのとは役割が逆です。

設定​

authorization_server.extensionに2つの項目を設定します。

設定項目説明
scope_resource_mappingリソース識別子と、そのリソースに属するスコープの対応
default_resource_indicatorどのリソースにも対応づかないときに使う既定値
{
"extension": {
"access_token_type": "JWT",
"scope_resource_mapping": {
"https://api.example.com": ["account", "profile"],
"https://payments.example.com": ["payments"]
},
"default_resource_indicator": "https://api.example.com"
}
}

この設定で発行されるトークンはこうなります。

要求スコープaud
accounthttps://api.example.com
paymentshttps://payments.example.com
account paymentsinvalid_scopeで拒否
どれにも対応づかないスコープのみhttps://api.example.com(既定値)

リソース識別子の書き方​

キーは絶対URIでなければならず、フラグメント(#)を含められません(RFC 8707 §2)。満たさない値は無視され、警告ログが出ます。

"scope_resource_mapping": {
"https://api.example.com": ["account"], // OK
"my-api": ["account"], // 無視される(URIでない)
"https://api.example.com/#v1": ["admin"] // 無視される(フラグメント)
}

クライアントIDを書いても採用されません。audがリソースではなくクライアントを指してしまう誤りを避けるためです。

プロトコルスコープは対応づけない​

openidのように全リクエストに付くスコープは、リソースに対応づけないでください。対応づけるとそのリソースが全リクエストの当事者になり、他のリソースを併せて要求した瞬間に必ず拒否されます。

// NG: openid を含む要求が payments を併せて要求できなくなる
"https://api.example.com": ["openid", "account"]

// OK
"https://api.example.com": ["account"]

複数リソースへの要求が拒否される理由​

1つのアクセストークンは1つのリソースしか名乗れません。複数を名乗るトークンは、それぞれのリソースが他所向けに発行されたスコープを受け入れることを許してしまいます。

拒否はスコープが決まる場所で行われます。

スコープが決まる場所返り方
認可リクエストリダイレクトでのinvalid_scope
CIBAバックチャネル認証リクエストレスポンスボディのinvalid_scope(リダイレクト先が無いため)
トークンリクエスト(client_credentials / password / jwt-bearer)レスポンスボディのinvalid_scope

リフレッシュでは検証しません。付与済みのグラントのスコープは変えられないため、設定変更を理由に既発行トークンのリフレッシュを失敗させないためです。

段階的な導入​

設定はオプトインです。scope_resource_mappingが空のテナントでは検証自体が走らず、audにはissuerが入ります。

警告

認可サーバー設定の更新は全置換です。マージされません。extensionだけを送ると、他の必須項目が欠けて400になります。

必ずGETで現在の設定を取得し、そこに項目を足して全体をPUTしてください。

1. 現在の設定を取得する​

curl -sS -X GET \
https://api.local.test/v1/management/tenants/{tenant-id}/authorization-server \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-o authorization-server.json

2. 対応表を足して全体をPUTする​

取得したJSONのextensionに2項目を追加します。

jq '.extension.scope_resource_mapping = {
"https://api.example.com": ["account"]
}' authorization-server.json > updated.json

curl -sS -X PUT \
https://api.local.test/v1/management/tenants/{tenant-id}/authorization-server \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-d @updated.json

対応づけたスコープだけが影響を受けます。対応表に無いスコープの挙動は変わりません。

3. 差し戻す​

対応表を空にすれば、導入前の挙動(audはissuer、スコープの組み合わせは無制限)に戻ります。

jq '.extension.scope_resource_mapping = {}' authorization-server.json > rollback.json

curl -sS -X PUT \
https://api.local.test/v1/management/tenants/{tenant-id}/authorization-server \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-d @rollback.json
ヒント

リソースを1つずつ追加していけば、影響範囲を確認しながら進められます。既に複数のスコープを併せて要求しているクライアントがある場合、それらを同じリソースに対応づけるか、別々のトークンを取得するようクライアント側を変更するかの判断が必要になります。

識別子型トークンの場合​

access_token_type: "opaque"のテナントではトークンにクレームが無いため、audは現れません。ただしスコープの検証は同じように行われます。リソースの宣言はトークン形式に依らず適用されるためです。

関連ドキュメント​

参考仕様​