Resource Indicators for OAuth 2.0

# はじめに

『**[Resource Indicators for OAuth 2.0](https://datatracker.ietf.org/doc/draft-ietf-oauth-resource-indicators/?include_text=1)**』という仕様を実装したので、それについて書こうと思います。

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Finished implementing &quot;Resource Indicators for <a href="https://twitter.com/hashtag/OAuth?src=hash&amp;ref_src=twsrc%5Etfw">#OAuth</a> 2.0&quot;. Now, by using <a href="https://twitter.com/hashtag/Authlete?src=hash&amp;ref_src=twsrc%5Etfw">#Authlete</a> 2.2, endpoints defined in RFC 6749, RFC 8628 and CIBA can recognize the &#39;resource&#39; request parameters and tokens become audience-restricted. It took me nearly 2 days to implement the spec.</p>&mdash; Taka@Authlete, BaaS for OAuth 2.0 &amp; OpenID Connect (@darutk) <a href="https://twitter.com/darutk/status/1203253526568112128?ref_src=twsrc%5Etfw">December 7, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

**追記**
2020 年 2 月に **[RFC 8707](https://tools.ietf.org/html/rfc8707)** としてリリースされました。この仕様は Authlete 2.2 以降でサポートされます。

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

この仕様により、`resource` という名前のリクエストパラメーターが定義され、[認可エンドポイント](https://tools.ietf.org/html/rfc6749#section-3.1)（[RFC 6749](https://tools.ietf.org/html/rfc6749)）や[トークンエンドポイント](https://tools.ietf.org/html/rfc6749#section-3.2)（[RFC 6749](https://tools.ietf.org/html/rfc6749)）がそれを解釈するようになります。**[Authlete](https://www.authlete.com/)**（オースリート）の実装（バージョン 2.2 以降）では、これらのエンドポイント群に加え、[バックチャネル認証エンドポイント](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html#rfc.section.7)（[CIBA](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html)）や[デバイス認可エンドポイント](https://tools.ietf.org/html/rfc8628#section-3.1)（[RFC 8628](https://tools.ietf.org/html/rfc8628)）も `resource` パラメーターを解釈します。

**`resource` パラメーターを伴うフローの結果生成されたアクセストークンは、指定されたリソースに対してのみ利用が可能となります。**このように、利用対象を制限されたアクセストークンは『[Audience Restricted Access Token](https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13#section-4.8.1.3)』と呼ばれます。

利用対象を制限されたアクセストークンの情報を[イントロスペクションエンドポイント](https://tools.ietf.org/html/rfc7662#section-2)（[RFC 7662](https://tools.ietf.org/html/rfc7662)）を用いて取得すると、`aud`（audience の意）プロパティーに対象となるリソース群が列挙されていることを確認できます。

```json
{
  "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](https://tools.ietf.org/html/rfc6749) に書かれている「Request and response parameters MUST NOT be included more than once」という規定に明確に違反しています。

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

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

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

# 例

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

```http
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 つだけです。

```http
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
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 形式のようなので、ペイロード部をデコードしてみると、

```json
{
  "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/` であることに注目してください。

```http
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
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"
}
```

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

```json
{
  "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` プロパティーの値は次のように文字列の配列とします。

```json
"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](https://datatracker.ietf.org/doc/draft-lodderstedt-oauth-rar/?include_text=1)**』という仕様の「2.3. Relationship to "resource" parameter」を実装したいと思ったので、その前提となる『**[Resource Indicators for OAuth 2.0](https://datatracker.ietf.org/doc/draft-ietf-oauth-resource-indicators/?include_text=1)**』を実装してみました。影響範囲は広かったものの、作業を開始してから 2 日で実装できました！

Resource Indicators for OAuth 2.0 は Authlete 2.2 以降で利用可能です。詳細は [Authlete 社](https://www.authlete.com/)まで[お問い合わせ](https://www.authlete.com/ja/contact/)ください！