OAuth 2.0 の認可レスポンスとリダイレクトに関する説明

# **はじめに**

この記事では、OAuth 2.0 の認可サーバーが返す**認可レスポンス**と、それに伴う**リダイレクト処理**について説明します。

動画解説のほうがお好みであれば、オンライン勉強会『[OAuth & OIDC 勉強会 【認可リクエスト編】](https://www.authlete.com/ja/resources/videos/20200527/)』の『[3. リダイレクション](https://www.youtube.com/watch?v=UpZycWvntpI&list=PLxDcFnLrbxvZs5eWfxDFdPjrzZ2R7h2Uw&index=5)』をご覧ください。

# **認可レスポンス**

[RFC 6749](https://tools.ietf.org/html/rfc6749)（The OAuth 2.0 Authorization Framework）には、アクセストークン発行フローが幾つか定義されています（参考：[OAuth 2.0 全フローの図解と動画](https://qiita.com/TakahikoKawasaki/items/200951e5b5929f840a1f)）。

それらのうち、**認可コードフロー**（RFC 6749, [4.1. Authorization Code Grant](https://tools.ietf.org/html/rfc6749#section-4.1)）と**インプリシットフロー**（RFC 6749, [4.2. Implicit Grant](https://tools.ietf.org/html/rfc6749#section-4.2)）では、クライアントアプリケーションが **Web ブラウザを介して**認可サーバーの**認可エンドポイント**（RFC 6749, [3.1. Authorization Endpoint](https://tools.ietf.org/html/rfc6749#section-3.1)）に**認可リクエスト**を投げるところから処理が始まります。

「Web ブラウザを介して」というのが分かりにくいですが、これはどういうことかというと、クライアントアプリケーションが Web ブラウザを起動し、

![web-browser.png](https://qiita-image-store.s3.amazonaws.com/0/106044/6cb1ab7f-ec20-639c-6c91-8b33a2ae1708.png)

その Web ブラウザに認可リクエストを表す URL を渡す、ということです。

![web-browser-authorization-request.png](https://qiita-image-store.s3.amazonaws.com/0/106044/6d4f6511-a267-dca4-75ce-6f8c9399beab.png)

認可リクエストを表す URL を受け取った Web ブラウザは、認可サーバーとのやりとりを開始します。通常、Web ブラウザが認可サーバーから最初に受け取るのは、**認可画面**と呼ばれる HTML ページです。

![authorization-page.png](https://qiita-image-store.s3.amazonaws.com/0/106044/640934c9-4a12-5d7c-87ae-bd2a3243cf1e.png)

認可画面には、認可リクエストに関する情報（クライアントアプリケーションに関する情報や要求されている権限の一覧など）が表示されます。ユーザーは、その内容を確認し、当該認可リクエストを承認するか拒否するかを決定します。

ユーザーが承認もしくは拒否のボタンを押すと、Web ブラウザは認可サーバーにその旨を伝えます。技術的には、Web ブラウザが認可サーバーに対して HTTP リクエストを投げます。

その HTTP リクエストを受け取った認可サーバーは、いろいろ処理をおこなったあと、Web ブラウザに対して応答（HTTP レスポンス）を返します。この応答のことを**認可レスポンス**と言います。


# **認可レスポンスはリダイレクトを発生させる**

認可レスポンスを直接受け取るのは、クライアントアプリケーションではなく、Web ブラウザです。そのため、認可レスポンスに含まれているレスポンスパラメーター群をクライアントアプリケーションに渡すためには、何らかの工夫が必要です。

<img width="827" alt="how-to-pass-response-to-client.png" src="https://qiita-image-store.s3.amazonaws.com/0/106044/f74e89f0-30f2-2ba7-0c9c-ec85e2dd9085.png">

そして、その工夫とは、**リダイレクト**を使うことです。

一般的には、Web サーバーが成功応答を返すときは、200 番台の HTTP ステータスコードを用います。一方、認可サーバーは認可レスポンスを返す際、302 という HTTP ステータスコードを用います（302 ではないケースについては後述）。また、併せて認可レスポンスに `Location` HTTP ヘッダーも含めます。 

つまり、認可レスポンスの内容は次のようになります。

```http
HTTP/1.1 302 Found
Location: {遷移先}
```

Web ブラウザは、HTTP ステータスコードが 302 の HTTP レスポンスを受け取ると、そのレスポンスに含まれている `Location` HTTP ヘッダーが示す場所に遷移しようとします。

たとえば、Web ブラウザが次のような HTTP レスポンスを受け取ったとすると、

```http
HTTP/1.1 302 Found
Location: https://www.authlete.com/
```

Web ブラウザは https://www.authlete.com/ に遷移しようとするので、ほどなくして Web ブラウザの表示領域に Authlete（オースリート）社のウェブサイトの内容が表示されることになります。

このように、302 という HTTP ステータスコードと `Location` ヘッダーを用いることで、認可サーバーは Web ブラウザを別の場所に遷移させる（**リダイレクト**する）ことができます。


# **遷移先が認可レスポンスパラメーター群を受け取る**

認可レスポンスの `Location` の値は、「遷移先」と「認可レスポンスパラメーター群」で構成されています。

```http
HTTP/1.1 302 Found
Location: {遷移先}{認可レスポンスパラメーター群}
```

Web ブラウザがこの「{遷移先}{認可レスポンスパラメーター群}」を処理することにより、{遷移先} に {認可レスポンスパラメーター群} が渡ることになります。

例えば、{遷移先} が `https://example.com/callback` で {認可レスポンスパラメーター群} が `?code=123&state=abc` だった場合、

```http
HTTP/1.1 302 Found
Location: https://example.com/callback?code=123&state=abc
```

Web ブラウザは `example.com` のウェブサーバーの `/callback` というパスに、`code=123&state=abc` というクエリーパラメーターを添えてアクセスしにいきます。

```http
GET /callback?code=123&state=abc HTTP/1.1
Host: example.com
```

結果的に、`https://example.com/callback` で待ち受けるプログラムが、`code=123&state=abc` という認可リクエストパラメーター群を受け取ることになります。

![redirection.png](https://qiita-image-store.s3.amazonaws.com/0/106044/19b45c7b-1e2d-a330-1c2e-c71fd621fe71.png)

このとき、遷移先である `https://example.com/callback` が、クライアントアプリケーションそのものの一部であれば、クライアントアプリケーションは認可レスポンスパラメーター群を受け取ることができます。また、遷移先がクライアントアプリケーションの一部ではなかったとしても、クライアントアプリケーションの開発者と遷移先プログラムの開発者が同一人物であれば、遷移先プログラムが受け取った認可レスポンスパラメーター群を何らかの方法でクライアントアプリケーションに渡すこと、もしくは、間接的にクライアントアプリケーションに利用させることが可能でしょう。

いずれにしても、遷移先がクライアントアプリケーション開発者の管理下にあれば、クライアントアプリケーションは認可レスポンスパラメーター群を受け取ることができます。


# **遷移先を事前に登録しておく**

そういうわけで、クライアントアプリケーション開発者は、認可レスポンスパラメーター群を受け取る場所となる遷移先を、事前に認可サーバーに登録しておきます。この、事前に登録する遷移先のことを『**リダイレクト URI**』と呼びます。

たとえば、クライアントアプリケーションがリダイレクト URI として `https://client.com/callback` を事前登録しておけば、認可レスポンスは次のようになるでしょう。

```http
HTTP/1.1 302 Found
Location: https://client.com/callback{認可レスポンスパラメーター群}
```

OAuth 2.0 にもとづく Web API を公開しているウェブサービスは、大抵の場合、クライアントアプリケーション開発者がクライアントアプリケーション群を管理するためのウェブコンソールを提供しています。その管理コンソールには、リダイレクト URI を登録する場所がどこかに必ずあるはずです。


# **複数のリダイレクト URI を登録してある場合**

クライアントアプリケーション用にリダイレクト URI が複数登録されている場合、どのリダイレクト URI を用いるべきか決められないので、認可サーバーは認可レスポンスを生成する際に困ってしまいます。そのため、複数のリダイレクト URI を登録しているクライアントアプリケーションは、認可リクエストを投げる際、`redirect_uri` というリクエストパラメーターを使用して、どのリダイレクト URI を用いて欲しいかを指定します。なお、OpenID Connect では、登録されているリダイレクト URI の数が一つだとしても、`redirect_uri` リクエストパラメーターは必ず指定しなければなりません。

![redirect_uri.png](https://qiita-image-store.s3.amazonaws.com/0/106044/b0822efa-7d8d-9a1c-bd7d-271650ba631e.png)

認可コードフローでは、認可レスポンスを受け取ったあと、クライアントアプリケーションは続けて、認可サーバーのトークンエンドポイントにトークンリクエストを投げます。認可リクエストで `redirect_uri` リクエストパラメーターを指定した場合は、トークンリクエストにおいても、同じ値を持つ `redirect_uri` リクエストパラメーターを指定する必要があるので、注意してください。

```http
POST {トークンエンドポイント} HTTP/1.1
Host: {認可サーバー}
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code   // 必須
&code={認可コード}                // 必須　認可エンドポイントのレスポンスに含まれる値を指定
&redirect_uri={リダイレクトURI}   // 認可リクエストに redirect_uri が含まれていれば必須
&code_verifier={ベリファイア}     // 認可リクエストに code_challenge が含まれていれば必須
```


# **認可リクエストパラメーター群が埋め込まれる場所**

認可コードフローでは、認可レスポンスパラメーター群は、クエリー部に埋め込まれる約束になっています。クエリー部とは、URL 内で `?` に続く部分のことです。

認可リクエストの `response_type` リクエストパラメーターの値が `code` の場合、

![response_type=code.png](https://qiita-image-store.s3.amazonaws.com/0/106044/ecc29217-324f-f183-bbd7-54bc6e9ae02d.png)

それは認可コードフローですので、認可レスポンスパラメーター群はクエリー部に埋め込まれます。下記の認可レスポンスの例で、`code=123&state=abc` が `?` の後に続くことに注目してください。

```http
HTTP/1.1 302 Found
Location: https://client.com/callback?code=123&state=abc
```

一方、インプリシットフローでは、認可レスポンスパラメーター群は、フラグメント部に埋め込まれる約束になっています。フラグメント部とは、URL 内で `#` に続く部分のことです。

認可リクエストの `response_type` リクエストパラメーターの値が `token` の場合、

![response_type=token.png](https://qiita-image-store.s3.amazonaws.com/0/106044/670ae5be-2f04-ab10-a0b8-a961a3eff081.png)

それはインプリシットフローですので、認可レスポンスパラメーター群はフラグメント部に埋め込まれます。下記の認可レスポンスの例で、`access_token=xyz&token_type=Bearer` が `#` の後に続くことに注目してください。

```http
HTTP/1.1 302 Found
Location: https://client.com/callback#access_token=xyz&token_type=Bearer
```


# **フラグメント部は HTTP リクエストに含まれない**

Web ブラウザは HTTP リクエストを投げる際、「クエリー部のパラメーター群は HTTP リクエストに含めるがフラグメント部のパラメーター群は HTTP リクエストに含めない」、という動作をします。

例えば、Web ブラウザのアドレスバーの部分に `https://example.com/callback?q=Q#f=F` という URL を入力すると、

![query_parameter_and_fragment_parameter.png](https://qiita-image-store.s3.amazonaws.com/0/106044/110b70e0-0c8f-0a5e-c5af-03c200e91e25.png)

クエリー部の `q=Q` は `https://example.com/callback` に渡されますが、フラグメント部の `f=F` は渡されません。下記の HTTP リクエストの例で、`?q=Q` は含まれているけれども `#f=F` は含まれていないことに注目してください。

```http
GET /callback?q=Q HTTP/1.1
Host: example.com
```

このため、`https://example.com/callback` で待ち受けるプログラムは、フラグメント部の情報を知ることはできません。

認可レスポンスパラメーター群は、認可コードフローではクエリー部に、インプリシットフローではフラグメント部に置かれますが、この些細な違いは、上記の Web ブラウザの動作を意識したものです。


# **JavaScript はフラグメント部にアクセスできる**

インプリシットフローでは認可レスポンスパラメーター群がフラグメント部に埋め込まれますが、HTTP リクエストにフラグメント部が含まれないとすると、遷移先で待ち受けるプログラムは認可レスポンスパラメーター群を取得することはできません。これはどういうことでしょうか？

実は、インプリシットフローにおいては、遷移先で動くプログラムは、認可レスポンスパラメーター群を取得しようとはしません。かわりに、Web ブラウザに対して、JavaScript を含む HTML を送り返します。この JavaScript そのものがクライアントアプリケーションです。

```http
HTTP/1.1 200 OK
Content-Type: text/html

<html>
......
<script src="client.js"></script>
</html>
```

JavaScript は、Web ブラウザに読み込まれると、Web ブラウザ内で動作します。そして、Web ブラウザ内で動作する JavaScript は、フラグメント部を含む現在の URL 情報にアクセスすることができます。このため、JavaScript アプリケーションであれば、フラグメント部に埋め込まれた認可レスポンスパラメーター群を取得できます。

![redirection-in-implicit-flow.png](https://qiita-image-store.s3.amazonaws.com/0/106044/1f854935-16d0-b316-fb8b-fb99efdeb98a.png)

ちなみに、URL のフラグメント部は次の JavaScript コードで取得できます。

```js
// URL のフラグメント部を取得する。
var fragment = window.location.hash;
```


# **インプリシットフローの使い所**

これまでの技術説明を踏まえると、インプリシットフローは、「Web ブラウザ内で動く JavaScript アプリケーションにアクセストークンを直接発行したいときに使う」ということが言えます。

クエリー部ではなくフラグメント部を使うことで、（１）Web ブラウザとリダイレクト URI 先との通信の中を（アクセストークンを含む）フラグメント部の情報が流れない、その結果、（２）リダイレクト URI 先の Web サーバーのログに（アクセストークンを含む）フラグメント部の情報が記録されることはない、という利点が得られます。これにより、アクセストークン漏洩のリスクを下げることができます。


# **インプリシットフロー非推奨**

RFC 6749 の発行から年月がたった現在、Web ブラウザ内で動く JavaScript アプリケーションにおいても**インプリシットフローを使うべきではない**（かわりに認可コードフローを使うべき）というのがベストプラクティスとなっています（[OAuth 2.0 Security Best Current Practice](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics)）。

[Torsten Lodderstedt](https://www.linkedin.com/in/torsten-lodderstedt-37528a1/) 氏は _"[Why you should stop using the OAuth implicit grant!](https://medium.com/@torsten_lodderstedt/why-you-should-stop-using-the-oauth-implicit-grant-2436ced1c926)"_（2018 年 11 月 9 日）という記事で、CORS（Cross-Origin Resource Sharing）が普及したことによりインプリシットフローを使う必要がなくなったと説明していますが、この件については @ritou さんの考察をご参照ください：『[OAuth 2.0 の Implicit grant 終了のお知らせ](https://ritou.hatenablog.com/entry/2018/11/12/110613)』


# **カスタムスキームではじまるリダイレクト URI**

リダイレクト URI の値として、`http:` や `https:` ではじまる URL を登録しておくと、認可レスポンスを受け取った Web ブラウザは、その URL が示す Web サーバーにアクセスしにいくことになります。これはつまり、`http:` や `https:` ではじまる URL をリダイレクト URI として使うということは、その URL が指す先に Web サーバーを用意しておかなければならないということです。

何らかのバックエンドの Web システムと連携するクライアントアプリケーションであれば、そのような Web サーバーを用意することが適切かもしれません。一方で、スマートフォンアプリとして単体で動くクライアントアプリケーションの場合、認可レスポンスパラメーター群を受け取るためにわざわざ Web サーバーを用意するのは手間がかかります。

そこで、`http` や `https` ではないカスタムスキームではじまる URI をリダイレクト URI として用い、そのカスタムスキームにクライアントアプリケーション自身が反応するように事前に設定しておきます。そうすることで、外部の Web サーバーを介さずともクライアントアプリケーションが認可レスポンスパラメーター群を取得することができるようになります。

たとえば、`myapp` というカスタムスキームではじまる `myapp://myhost/` というリダイレクト URI を登録しておくと、認可レスポンスは次のようになります。

```http
HTTP/1.1 302 Found
Location: myapp://myhost/{認可レスポンスパラメーター群}
```

この認可レスポンスを受け取った Web ブラウザは、`Location` ヘッダーに書かれた URI を処理しようとしますが、そのスキームが `myapp` という見慣れないものであるため、自分自身では処理できません。そこで、`myapp` スキームではじまる URI を処理できる外部プログラムがシステム内に存在するかどうかを調べます。そして、もし存在すれば、その外部プログラムに処理を委譲します。

ここで、クライアントアプリケーションが `myapp` スキームを処理できる外部プログラムとして自分自身を事前登録していたならば、Web ブラウザはクライアントアプリケーションに `Location` ヘッダーの値を渡すことになります。このような手順を踏むことで、めでたくクライアントアプリケーションに認可レスポンスパラメーター群が渡ってきます。

カスタムスキームに反応する外部アプリケーションを登録する方法はシステム毎に異なります。例えば Android であれば、`AndroidManifest.xml` に次のような設定を追加することでカスタムスキームに反応できるようになります。

```xml
<data android:scheme="myapp" android:host="myhost" />
```

カスタムスキームに反応する外部アプリケーションの登録方法については、各システムの技術文書を参照してください。


# **App-Claimed https**

2018 年 10 月 24 日に承認された<sup>※</sup>『[Financial-grade API - Part 1: Read-Only API Security Profile](https://openid.net/specs/openid-financial-api-part-1.html)』の Implementer's Draft 第 2 版の「[5.2.2. Authorization server](https://openid.net/specs/openid-financial-api-part-1.html#authorization-server)」には、_"shall require redirect URIs to use the https scheme"_ という要求事項があります。

<sup>※：[Implementer’s Drafts of Three FAPI Specifications Approved](https://openid.net/2018/10/24/implementers-drafts-of-three-fapi-specifications-approved/) (Oct. 24, 2018)</sup>

これはつまり、FAPI 対応の認可サーバーとやりとりするクライアントアプリケーションが用いるリダイレクト URI のスキームは、必ず `https` でなければならないということです。裏を返すと、カスタムスキームは使えません。

カスタムスキームは使えないけれども、外部 Web サーバーを立てずにクライアント側だけでリダイレクト処理を完結させたい場合、[BCP 212](https://tools.ietf.org/html/bcp212)（OAuth 2.0 for Native Apps）の「[7.2. Claimed "https" Scheme URI Redirection](https://tools.ietf.org/html/bcp212#section-7.2)」で言及されている手法を使うことになります。

一部の OS では、アプリケーションの管理下にあるドメインをホスト部に用いた `https://` で始まる URI を、特別に扱うことができます。そのような URI に出会うと、ブラウザはそのページを読み込むのではなく、その URI を引数として外部アプリケーションを起動します。この動作により、クライアントアプリケーションは、カスタムスキームを用いたときと同様に、リダイレクトエンドポイントの処理を自ら実装することができます。

この、『**app-claimed https**』機能ですが、全ての OS で使えるわけではないので注意してください。FAPI ワーキンググループのある方からの情報によると、iOS と Android では使えるものの、Windows Mobile、Windows デスクトップ、Mac OS では使えないそうです。


# **認可コード横取り攻撃**

同じカスタムスキームに反応する外部アプリケーションが複数登録されている場合、認可リクエストを発行したクライアントアプリケーション以外のアプリケーションが認可レスポンスを受け取ってしまう可能性がでてきてしまいます。

実際、悪意のあるアプリケーションが、わざと他のアプリケーションが用いているカスタムスキームに反応するように自分自身を登録し、認可サーバーが発行した認可コードを横取りしてしまう攻撃（authorization code interception attack）が発生しました。悪意のあるアプリケーションは、横取りした認可コードを用いて認可サーバーのトークンエンドポイントにアクセスし、アクセストークンを取得することができてしまいました。

![authorization-code-interception-attack.png](https://qiita-image-store.s3.amazonaws.com/0/106044/a59e314b-0ea3-e757-77bc-fde855a230ae.png)

この攻撃に対する対抗策として策定されたのが、[RFC 7636](https://tools.ietf.org/html/rfc7636)（Proof Key for Code Exchange by OAuth Public Clients）、通称 **PKCE**（ピクシー）と呼ばれる仕様です。

『認可コードフロー』かつ『カスタムスキーム』を用いるクライアントアプリケーションは、この PKCE の仕様に準拠して、認可リクエストに `code_challenge` リクエストパラメーターをつけることが必須となります（仕様上必須ではないですがセキュリティー的観点から必須です）。もちろん認可サーバー側でも PKCE をサポートしておかなければなりません。

PKCE に準拠するためにクライアント側とサーバー側でやらなければならないことについては、『[PKCE: 認可コード横取り攻撃対策のために OAuth サーバーとクライアントが実装すべきこと](https://qiita.com/TakahikoKawasaki/items/00f333c72ed96c4da659)』という記事を参照してください。


# **response_mode リクエストパラメーター**

認可レスポンスパラメーター群は、認可コードフローであればクエリー部に、インプリシットフローであればフラグメント部に置かれます。このデフォルトの動作を変更するリクエストパラメーターが `response_mode` パラメーターです。

`response_mode` に `query` を指定すると認可レスポンスパラメーター群はクエリー部に、`fragment` を指定するとフラグメント部に置かれるようになります。

ただし、インプリシットフローでは `response_mode=query` を指定してはいけないことになっています。ですので、`response_mode` リクエストパラメーターでできることは、せいぜい、認可コードフローの認可レスポンスパラメーター群をフラグメント部に置くように `response_mode=fragment` と指定することぐらいです。

![response_mode=fragment.png](https://qiita-image-store.s3.amazonaws.com/0/106044/3b73930d-bbdd-4211-5821-7eb917a5e1d2.png)

下記の例では、認可コード（`code`）がフラグメント部（`#` に続く部分）に埋め込まれています。

```http
HTTP/1.1 302 Found
Location: https://client.com/callback#code=123&state=abc
```


# **response_mode=form_post**

`response_mode` リクエストパラメーターが `query` と `fragment` しか受け付けないのであれば、たいして有用ではありません。しかし、[OAuth 2.0 Form Post Response Mode](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html) という仕様で定義されている `form_post` は意義ある変化をもたらします。

これまでの説明では、認可レスポンスの HTTP ステータスは 302 でした。しかし、`response_mode=form_post` というリクエストパラメーターを認可リクエストにつけると、認可レスポンスの HTTP ステータスコードは 200 となり、そのレスポンスボディーに HTML が含まれるようになります。

次の認可レスポンスの例は、当該仕様書から抜粋したものです。

```http
HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Cache-Control: no-cache, no-store
Pragma: no-cache

<html>
 <head><title>Submit This Form</title></head>
 <body onload="javascript:document.forms[0].submit()">
  <form method="post" action="https://client.example.org/callback">
    <input type="hidden" name="state"
     value="DcP7csa3hMlvybERqcieLHrRzKBra"/>
    <input type="hidden" name="id_token"
     value="eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJzdWIiOiJqb2huIiw
       iYXVkIjoiZmZzMiIsImp0aSI6ImhwQUI3RDBNbEo0c2YzVFR2cllxUkIiLC
       Jpc3MiOiJodHRwczpcL1wvbG9jYWxob3N0OjkwMzEiLCJpYXQiOjEzNjM5M
       DMxMTMsImV4cCI6MTM2MzkwMzcxMywibm9uY2UiOiIyVDFBZ2FlUlRHVE1B
       SnllRE1OOUlKYmdpVUciLCJhY3IiOiJ1cm46b2FzaXM6bmFtZXM6dGM6U0F
       NTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZCIsImF1dGhfdGltZSI6MTM2Mz
       kwMDg5NH0.c9emvFayy-YJnO0kxUNQqeAoYu7sjlyulRSNrru1ySZs2qwqq
       wwq-Qk7LFd3iGYeUWrfjZkmyXeKKs_OtZ2tI2QQqJpcfrpAuiNuEHII-_fk
       IufbGNT_rfHUcY3tGGKxcvZO9uvgKgX9Vs1v04UaCOUfxRjSVlumE6fWGcq
       XVEKhtPadj1elk3r4zkoNt9vjUQt9NGdm1OvaZ2ONprCErBbXf1eJb4NW_h
       nrQ5IKXuNsQ1g9ccT5DMtZSwgDFwsHMDWMPFGax5Lw6ogjwJ4AQDrhzNCFc
       0uVAwBBb772-86HpAkGWAKOK-wTC6ErRTcESRdNRe0iKb47XRXaoz5acA"/>
  </form>
 </body>
</html>
```

レスポンスボディー内の HTML には `form` タグがあります。この `form` タグの `method` には `post` が設定され、`action` にはリダイレクト URI である `https://client.example.org/callback` が設定されています。そして、`<form>` と `</form>` で囲まれている範囲には二つの `input` タグがあり、それぞれ `state` と `id_token` というパラメーターを表しています。

この HTML のポイントは、`body` タグの `onload` に

```js
javascript:document.forms[0].submit()
```

と書かれていることです。これにより、Web ブラウザがこの HTML を読み込んだあと、フォームが自動的に送信されます。

結果として、上記の認可レスポンスを受け取った Web ブラウザは、`client.example.org` に対して次のような HTTP リクエストを発行することになります。

```http
POST /callback HTTP/1.1
Host: client.example.org
Content-Type: application/x-www-form-urlencoded

id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJzdWIiOiJqb2huIiwiYX
       VkIjoiZmZzMiIsImp0aSI6ImhwQUI3RDBNbEo0c2YzVFR2cllxUkIiLCJpc
       3MiOiJodHRwczpcL1wvbG9jYWxob3N0OjkwMzEiLCJpYXQiOjEzNjM5MDMx
       MTMsImV4cCI6MTM2MzkwMzcxMywibm9uY2UiOiIyVDFBZ2FlUlRHVE1BSnl
       lRE1OOUlKYmdpVUciLCJhY3IiOiJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTD
       oyLjA6YWM6Y2xhc3NlczpQYXNzd29yZCIsImF1dGhfdGltZSI6MTM2MzkwM
       Dg5NH0.c9emvFayy-YJnO0kxUNQqeAoYu7sjlyulRSNrru1ySZs2qwqqwwq
       -Qk7LFd3iGYeUWrfjZkmyXeKKs_OtZ2tI2QQqJpcfrpAuiNuEHII-_fkIuf
       bGNT_rfHUcY3tGGKxcvZO9uvgKgX9Vs1v04UaCOUfxRjSVlumE6fWGcqXVE
       KhtPadj1elk3r4zkoNt9vjUQt9NGdm1OvaZ2ONprCErBbXf1eJb4NW_hnrQ
       5IKXuNsQ1g9ccT5DMtZSwgDFwsHMDWMPFGax5Lw6ogjwJ4AQDrhzNCFc0uV
       AwBBb772-86HpAkGWAKOK-wTC6ErRTcESRdNRe0iKb47XRXaoz5acA&
state=DcP7csa3hMlvybERqcieLHrRzKBra
```

この `form_post` の動作により、認可レスポンスパラメーター群が、クエリー部やフラグメント部ではなく、リクエストボディーに埋め込まれてリダイレクト URI 先に渡されることになります。

利点としては、まず、URL に認可レスポンスパラメーター群が含まれなくなることから、情報漏洩のリスクが下がります。また、仕様上フラグメント部にしか置けなかったパラメーター群（`token` や `id_token`）は、それまでリダイレクト URI 先に渡すことはできませんでしたが（Web ブラウザがフラグメント部を HTTP リクエストに含めないため）、リクエストボディーに埋め込むことで、リダイレクト URI 先にも渡せるようになりました。


# **JWT Secured Authorization Response Mode**

『[Financial-grade API: JWT Secured Authorization Response Mode for OAuth 2.0 (JARM)](https://openid.net/specs/openid-financial-api-jarm.html)』、通称 **JARM** という仕様があります。これは、最初の Public Review を経て 2018 年 10 月 22 日に投票締切日を迎えるという、かなり新しい仕様です。（この記事を書いているのが 2018 年 10 月 22 日）

この仕様は、`response_mode` リクエストパラメーターの値として、次の 4 つを新たに定義します。

1. `query.jwt`
2. `fragment.jwt`
3. `form_post.jwt`
4. `jwt`

これらのうちどれかを指定すると、認可レスポンスのレスポンスパラメーター群は一つの JWT（[RFC 7519](https://tools.ietf.org/html/rfc7519)）にまとめられて、`response` というレスポンスパラメーターとして返されます。

たとえば認可コードフローであれば、これまでは

```http
HTTP/1.1 302 Found
Location: https://client.com/callback?code=123&state=abc
```

というように、レスポンスパラメーター群が個別に `code` や `state` といった名前を持って返却されてきていたところ、認可リクエストに `response_mode=query.jwt` と付けると、次のような形で認可レスポンスが返ってきます。

```http
HTTP/1.1 302 Found
Location: https://client.com/callback?response={JWT}
```

`response` の値をデコードすると、ペイロード部に `code` や `state` といった、馴染みのあるレスポンスパラメーターを見つけることができます。

下記は、当該仕様書から抜粋した `response_mode=query.jwt` に対する認可レスポンスの例です。

```http
HTTP/1.1 302 Found
Location: https://client.example.com/cb?
response=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLm
V4YW1wbGUuY29tIiwiYXVkIjoiczZCaGRSa3F0MyIsImV4cCI6MTMxMTI4MTk3MCwiY29kZSI6IlB5eU
ZhdXgybzdRMFlmWEJVMzJqaHcuNUZYU1FwdnI4YWt2OUNlUkRTZDBRQSIsInN0YXRlIjoiUzhOSjd1cW
s1Zlk0RWpOdlBfR19GdHlKdTZwVXN2SDlqc1luaTlkTUFKdyJ9.HkdJ_TYgwBBj10C-aWuNUiA062Amq
2b0_oyuc5P0aMTQphAqC2o9WbGSkpfuHVBowlb-zJ15tBvXDIABL_t83q6ajvjtq_pqsByiRK2dLVdUw
KhW3P_9wjvI0K20gdoTNbNlP9Z41mhart4BqraIoI8e-L_EfAHfhCG_DDDv7Yg
```

`response=` の後に `eyJ` から始まる長い文字列が続いていますが、これが JWT です。この JWT をデコードすると、ペイロード部が次の内容を持つことがわかります。

```json
{
  "iss": "https://accounts.example.com",
  "aud": "s6BhdRkqt3",
  "exp": 1311281970,
  "code": "PyyFaux2o7Q0YfXBU32jhw.5FXSQpvr8akv9CeRDSd0QA",
  "state": "S8NJ7uqk5fY4EjNvP_G_FtyJu6pUsvH9jsYni9dMAJw"
}
```

`code` や `state` に加え、`iss`、`aud`、`exp` というパラメーター群も含まれていますが、これらは同仕様書の「[4.1. The JWT Response Document](https://openid.net/specs/openid-financial-api-jarm.html#the-jwt-response-document)」で説明されています。


# **JARM 関連のメタデータ**

JARM 仕様は、クライアントのメタデータとして次の 3 つを追加します（[5. Client Metadata](https://openid.net/specs/openid-financial-api-jarm.html#client-metadata)）。これらは、認可レスポンスパラメーター群を JWT にまとめるときに、その JWT の署名・暗号にどのアルゴリズムを使用してほしいかを指定するものです。

1. `authorization_signed_response_alg`
2. `authorization_encrypted_response_alg`
3. `authorization_encrypted_response_enc`

認可サーバーのメタデータとしては次の 3 つが追加されます（[6. Authorization Server Metadata](https://openid.net/specs/openid-financial-api-jarm.html#authorization-server-metadata)）。これらは、認可レスポンスパラメーター群を JWT にまとめるときに、その JWT の署名・暗号アルゴリズムとしてサーバーがサポートするアルゴリズムを列挙するものです。

1. `authorization_signing_alg_values_supported`
2. `authorization_encryption_alg_values_supported`
3. `authorization_encryption_enc_values_supported`

JARM をサポートする認可サーバーのディスカバリーエンドポイントからの応答には、これらのパラメーター群が含まれることになります。加えて、`response_modes_supported` の値として、新たに `query.jwt`、`fragment.jwt`、`form_post.jwt`、`jwt` が追加されることになります。


## **JARM の実装状況**

出来たばかりの仕様ですが、[Authlete 社](https://www.authlete.com/)は 2018 年 10 月 2 日時点で実装を終わらせています。もしかすると、世界で最初の実装かもしれません。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">現在 Public Review 期間中の仕様、『Financial-grade API: JWT Secured Authorization Response Mode for OAuth 2.0 (JARM)』、実装を終えました。response_mode=form_post.jwt も動きました。スクリーンショットは、Authlete が開発用に提供しているリダイレクトエンドポイントが表示した画面。 <a href="https://t.co/Ihsx11Kpmg">pic.twitter.com/Ihsx11Kpmg</a></p>&mdash; Takahiko Kawasaki (@darutk) <a href="https://twitter.com/darutk/status/1046959306627854336?ref_src=twsrc%5Etfw">2018年10月2日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

JARM の機能は Authlete 1.x 系の共用サーバー（`api.authlete.com`）では利用できませんが、Authlete 2.1 以降では利用可能です。詳細は support@authlete.com までお問い合わせください。

<sup>注：本記事の執筆者（私）は Authlete 社の創業者です。</sup>


## **おわりに**

認可レスポンスによるリダイレクト指示を受けたあと、Web ブラウザやリダイレクト URI 先のプログラム、クライアントアプリケーションがどのような動作をするのかについては、OAuth 2.0 の仕様範囲外ということもあり、詳細な解説記事をこれまで書いてきませんでした。

しかし最近、認可コードフローとインプリシットフローの違いを尋ねられることが増え、そのたびに、本記事で説明した内容をホワイトボードを使いながら口頭で説明している自分がいることに気がつきました。

これはもうやってられない、と。

このような背景のもと、本記事を書きました。書いてみて気付いたのは、「この分量の内容を口頭で説明されても消化しきれるわけない」ということでした。これまで私の説明を口頭で受けてきた方々、すみませんでした。今後は対面質疑応答ミーティングに先立ち、この記事を紹介させていただこうと思います。