Help us understand the problem. What is going on with this article?

Resource Indicators for OAuth 2.0

はじめに

Resource Indicators for OAuth 2.0』という仕様を実装したので、それについて書こうと思います。

追記
2020 年 2 月に RFC 8707 としてリリースされました。この仕様は Authlete 2.2 以降でサポートされます。

何ができるようになるのか

この仕様により、resource という名前のリクエストパラメーターが定義され、認可エンドポイントRFC 6749)やトークンエンドポイントRFC 6749)がそれを解釈するようになります。Authlete(オースリート)の実装(バージョン 2.2 以降)では、これらのエンドポイント群に加え、バックチャネル認証エンドポイントCIBA)やデバイス認可エンドポイントRFC 8628)も resource パラメーターを解釈します。

resource パラメーターを伴うフローの結果生成されたアクセストークンは、指定されたリソースに対してのみ利用が可能となります。このように、利用対象を制限されたアクセストークンは『Audience Restricted Access Token』と呼ばれます。

利用対象を制限されたアクセストークンの情報をイントロスペクションエンドポイントRFC 7662)を用いて取得すると、aud(audience の意)プロパティーに対象となるリソース群が列挙されていることを確認できます。

{
  "aud": [
    "https://cal.example.com/",
    "https://contacts.example.com/"
  ],
  ......
}

また、アクセストークンが JWT 形式の場合、その JWT のペイロード内の aud クレームに同様の情報が含まれます。

仕様違反問題

仕様によれば、resource パラメーターを 2 つ以上リクエストに含めることが許されています。

("2. Resource Parameter" より抜粋)

resource
Indicates the target service or resource to which access is being requested. Its value MUST be an absolute URI, as specified by Section 4.3 of [RFC3986]. The URI MUST NOT include a fragment component. It SHOULD NOT include a query component, but it is recognized that there are cases that make a query component a useful and necessary part of the resource parameter, such as when query parameter(s) are used to scope requests to an application. The "resource" parameter URI value is an identifier representing the identity of the resource, which MAY be a locator that corresponds to a network addressable location where the target resource is hosted. Multiple "resource" parameters MAY be used to indicate that the requested token is intended to be used at multiple resources.

しかしこれは、OAuth 2.0 の基本仕様である RFC 6749 に書かれている「Request and response parameters MUST NOT be included more than once」という規定に明確に違反しています。

この仕様違反の解決方法としては、(1)特例として扱うことを仕様書内に明記するか、(2)仕様違反を回避するために別の方法を考案するかのどちらかになると思っていましたが、仕様の著者が「当該規定は拡張仕様には適用されない」と解釈することにより「問題無し」という扱いにしようとしていると聞き及びました。

仕様著者の解釈について「なんだかなー」と思っている専門家が複数いることは知っていますが、これは著者が悪いというよりも、背景事情によりやむを得ない選択だったと考えられます。誤解のないように言っておきますと、著者は他にも様々な OAuth/OIDC 関連仕様を策定している専門家で、立派な方です。

背景としては、アクセストークンの対象リソースを制限する方法が幾つか考案・実装されていたため、どれか一つに統一して標準化したほうがよいというものがあります。Write Up によれば、同じような機能を Ping Identity 社は aud パラメーターで、Auth0 社は audience パラメーターで実現しています。これらの中で、Microsoft 社の resource パラメーターを軸にして Resource Indicators for OAuth 2.0 の策定が進められたようです。

下記は、仕様書から抜粋した認可コードフローによる認可リクエストの例(仕様書内の Figure 2)です。resource パラメーターが 2 つ含まれています。

GET /as/authorization.oauth2?response_type=code
   &client_id=s6BhdRkqt3
   &state=tNwzQ87pC6llebpmac_IDeeq-mCR2wLDYljHUZUAWuI
   &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
   &scope=calendar%20contacts
   &resource=https%3A%2F%2Fcal.example.com%2F
   &resource=https%3A%2F%2Fcontacts.example.com%2F HTTP/1.1
Host: authorization-server.example.com

面白いのは、この認可リクエストに対応するトークンリクエストで再び resource パラメーターを使ってもよいことです。下記は仕様書から抜粋したトークンリクエストの例(Figure 3)ですが、ここで使われている resource パラメーターは 1 つだけです。

POST /as/token.oauth2 HTTP/1.1
Host: authorization-server.example.com
Authorization: Basic czZCaGRSa3F0Mzpoc3FFelFsVW9IQUU5cHg0RlNyNHlJ
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
&code=10esc29BWC2qZB0acc9v8zAv9ltc2pko105tQauZ
&resource=https%3A%2F%2Fcal.example.com%2F

この結果、次のようなレスポンスが得られます(Figure 4)。

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-cache, no-store

{
   "access_token":"eyJhbGciOiJFUzI1NiIsImtpZCI6Ijc3In0.eyJpc3MiOi
   JodHRwOi8vYXV0aG9yaXphdGlvbi1zZXJ2ZXIuZXhhbXBsZS5jb20iLCJzdWI
   iOiJfX2JfYyIsImV4cCI6MTU4ODQyMDgwMCwic2NvcGUiOiJjYWxlbmRhciIs
   ImF1ZCI6Imh0dHBzOi8vY2FsLmV4YW1wbGUuY29tLyJ9.nNWJ2dXSxaDRdMUK
   lzs-cYIj8MDoM6Gy7pf_sKrLGsAFf1C2bDhB60DQfW1DZL5npdko1_Mmk5sUf
   zkiQNVpYw",
   "token_type":"Bearer",
   "expires_in":3600,
   "refresh_token":"4LTC8lb0acc6Oy4esc1Nk9BWC0imAwH7kic16BDC2",
   "scope":"calendar"
}

発行されたアクセストークンは JWT 形式のようなので、ペイロード部をデコードしてみると、

{
  "iss": "http://authorization-server.example.com",
  "sub": "__b_c",
  "exp": 1588420800,
  "scope": "calendar",
  "aud": "https://cal.example.com/"
}

aud の値が一つだけであり、トークンリクエストで指定した resource の値と一致していることが分かります。

これらの一連のフローが何をしているかというと、認可リクエストでは対象リソースを https://cal.example.com/https://contacts.example.com/ の二つとする認可コードを発行し、その認可コードを用いたトークンリクエストでは、より対象を絞って https://cal.example.com/ だけを指定し、そのリソースだけを対象とするアクセストークンの発行を受けています。

認可リクエストで指定するリソース群とトークンリクエストで指定するリソース群に差異を持たせることの意味が分かりにくいかと思いますが、認可リクエストで指定したリソース群は、リフレッシュトークンに影響を与えます。リフレッシュトークンが対象とするリソース群は、認可リクエストで指定した二つのリソース群となります。

Figure 4 で発行されたリフレッシュトークンを用いたトークンリクエストの例(Figure 5)は次のようになっています。Figure 3 のトークンリクエストの resource パラメーターの値が https://cal.example.com/ だったのに対し、今回は https://contacts.example.com/ であることに注目してください。

POST /as/token.oauth2 HTTP/1.1
Host: authorization-server.example.com
Authorization: Basic czZCaGRSa3F0Mzpoc3FFelFsVW9IQUU5cHg0RlNyNHlJ
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=4LTC8lb0acc6Oy4esc1Nk9BWC0imAwH7kic16BDC2
&resource=https%3A%2F%2Fcontacts.example.com%2F

このトークンリクエストに対するレスポンス例(Figure 6)は次の通りです。

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-cache, no-store

{
   "access_token":"eyJhbGciOiJFUzI1NiIsImtpZCI6Ijc3In0.eyJpc3MiOi
   JodHRwOi8vYXV0aG9yaXphdGlvbi1zZXJ2ZXIuZXhhbXBsZS5jb20iLCJzdWI
   iOiJfX2JfYyIsImV4cCI6MTU4ODQyMDgyNiwic2NvcGUiOiJjb250YWN0cyIs
   ImF1ZCI6Imh0dHBzOi8vY29udGFjdHMuZXhhbXBsZS5jb20vIn0.5f4yhqazc
   OSlJw4y94KPeWNEFQqj2cfeO8x4hr3YbHtIl3nQXnBMw5wREY5O1YbZED-GfH
   UowfmtNaA5EikYAw",
   "token_type":"Bearer",
   "expires_in":3600,
   "scope":"contacts"
}

発行されたアクセストークンのペイロード部をデコードしてみると、

{
  "iss": "http://authorization-server.example.com",
  "sub": "__b_c",
  "exp": 1588420826,
  "scope": "contacts",
  "aud": "https://contacts.example.com/"
}

今度は aud の値が https://contacts.example.com/ になっていることが分かります。

リクエストオブジェクトを使う場合

resource パラメーターを複数指定可能にしたため、リクエストオブジェクトを使用する際には注意が必要です。

リクエストオブジェクトのペイロード部分は JSON であるため、同じ名前のキーを複数持たせることができません。そのため、複数のリソースを指定したい場合は、リクエストオブジェクト内の resource プロパティーの値は次のように文字列の配列とします。

"resource": [
  "https://cal.example.com/",
  "https://contacts.example.com/"
]

これについては仕様書の「2.1. Authorization Request」の第二段落目に次のように明記されています。

For an authorization request sent as a JSON Web Token (JWT), such as when using JWT Secured Authorization Request [I-D.ietf-oauth-jwsreq], a single "resource" parameter value is represented as a JSON string while multiple values are represented as an array of strings.

実装上の注意点

同一名のリクエストパラメーターが複数回指定される可能性があることを考慮していない Web アプリケーションフレームワークでは、Resource Indicators for OAuth 2.0 を実装することはできません。

仕様自体は難しくないですが、下記のように影響範囲が広いので、第一印象よりも実装の手間がかかることになると思います。

  • 認可エンドポイント
    • 認可コードフロー
    • インプリシットフロー
    • リクエストオブジェクト
  • トークンエンドポイント
    • 認可コードフロー
    • リソースオーナー・パスワードクレデンシャルズ・フロー
    • クライアントクレデンシャルズ・フロー
    • CIBA POLL モード
    • CIBA PING モード
    • デバイスフロー
  • バックチャネル認証エンドポイント
    • CIBA POLL モード
    • CIBA PING モード
    • CIBA PUSH モード
    • リクエストオブジェクト
  • デバイス認可エンドポイント
    • デバイスフロー
  • イントロスペクションエンドポイント
  • JWT 形式アクセストークン

おわりに

OAuth 2.0 Rich Authorization Requests』という仕様の「2.3. Relationship to "resource" parameter」を実装したいと思ったので、その前提となる『Resource Indicators for OAuth 2.0』を実装してみました。影響範囲は広かったものの、作業を開始してから 2 日で実装できました!

Resource Indicators for OAuth 2.0 は Authlete 2.2 以降で利用可能です。詳細は Authlete 社までお問い合わせください!

TakahikoKawasaki
株式会社 Authlete の共同創業者。プログラマー兼代表取締役社長。
https://www.authlete.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした