はじめに
この記事では、OAuth 2.0 の認可サーバーが返す認可レスポンスと、それに伴うリダイレクト処理について説明します。
動画解説のほうがお好みであれば、オンライン勉強会『OAuth & OIDC 勉強会 【認可リクエスト編】』の『3. リダイレクション』をご覧ください。
認可レスポンス
RFC 6749(The OAuth 2.0 Authorization Framework)には、アクセストークン発行フローが幾つか定義されています(参考:OAuth 2.0 全フローの図解と動画)。
それらのうち、認可コードフロー(RFC 6749, 4.1. Authorization Code Grant)とインプリシットフロー(RFC 6749, 4.2. Implicit Grant)では、クライアントアプリケーションが Web ブラウザを介して認可サーバーの認可エンドポイント(RFC 6749, 3.1. Authorization Endpoint)に認可リクエストを投げるところから処理が始まります。
「Web ブラウザを介して」というのが分かりにくいですが、これはどういうことかというと、クライアントアプリケーションが Web ブラウザを起動し、
その Web ブラウザに認可リクエストを表す URL を渡す、ということです。
認可リクエストを表す URL を受け取った Web ブラウザは、認可サーバーとのやりとりを開始します。通常、Web ブラウザが認可サーバーから最初に受け取るのは、認可画面と呼ばれる HTML ページです。
認可画面には、認可リクエストに関する情報(クライアントアプリケーションに関する情報や要求されている権限の一覧など)が表示されます。ユーザーは、その内容を確認し、当該認可リクエストを承認するか拒否するかを決定します。
ユーザーが承認もしくは拒否のボタンを押すと、Web ブラウザは認可サーバーにその旨を伝えます。技術的には、Web ブラウザが認可サーバーに対して HTTP リクエストを投げます。
その HTTP リクエストを受け取った認可サーバーは、いろいろ処理をおこなったあと、Web ブラウザに対して応答(HTTP レスポンス)を返します。この応答のことを認可レスポンスと言います。
認可レスポンスはリダイレクトを発生させる
認可レスポンスを直接受け取るのは、クライアントアプリケーションではなく、Web ブラウザです。そのため、認可レスポンスに含まれているレスポンスパラメーター群をクライアントアプリケーションに渡すためには、何らかの工夫が必要です。
そして、その工夫とは、リダイレクトを使うことです。
一般的には、Web サーバーが成功応答を返すときは、200 番台の HTTP ステータスコードを用います。一方、認可サーバーは認可レスポンスを返す際、302 という HTTP ステータスコードを用います(302 ではないケースについては後述)。また、併せて認可レスポンスに Location
HTTP ヘッダーも含めます。
つまり、認可レスポンスの内容は次のようになります。
HTTP/1.1 302 Found
Location: {遷移先}
Web ブラウザは、HTTP ステータスコードが 302 の HTTP レスポンスを受け取ると、そのレスポンスに含まれている Location
HTTP ヘッダーが示す場所に遷移しようとします。
たとえば、Web ブラウザが次のような HTTP レスポンスを受け取ったとすると、
HTTP/1.1 302 Found
Location: https://www.authlete.com/
Web ブラウザは https://www.authlete.com/ に遷移しようとするので、ほどなくして Web ブラウザの表示領域に Authlete(オースリート)社のウェブサイトの内容が表示されることになります。
このように、302 という HTTP ステータスコードと Location
ヘッダーを用いることで、認可サーバーは Web ブラウザを別の場所に遷移させる(リダイレクトする)ことができます。
遷移先が認可レスポンスパラメーター群を受け取る
認可レスポンスの Location
の値は、「遷移先」と「認可レスポンスパラメーター群」で構成されています。
HTTP/1.1 302 Found
Location: {遷移先}{認可レスポンスパラメーター群}
Web ブラウザがこの「{遷移先}{認可レスポンスパラメーター群}」を処理することにより、{遷移先} に {認可レスポンスパラメーター群} が渡ることになります。
例えば、{遷移先} が https://example.com/callback
で {認可レスポンスパラメーター群} が ?code=123&state=abc
だった場合、
HTTP/1.1 302 Found
Location: https://example.com/callback?code=123&state=abc
Web ブラウザは example.com
のウェブサーバーの /callback
というパスに、code=123&state=abc
というクエリーパラメーターを添えてアクセスしにいきます。
GET /callback?code=123&state=abc HTTP/1.1
Host: example.com
結果的に、https://example.com/callback
で待ち受けるプログラムが、code=123&state=abc
という認可リクエストパラメーター群を受け取ることになります。
このとき、遷移先である https://example.com/callback
が、クライアントアプリケーションそのものの一部であれば、クライアントアプリケーションは認可レスポンスパラメーター群を受け取ることができます。また、遷移先がクライアントアプリケーションの一部ではなかったとしても、クライアントアプリケーションの開発者と遷移先プログラムの開発者が同一人物であれば、遷移先プログラムが受け取った認可レスポンスパラメーター群を何らかの方法でクライアントアプリケーションに渡すこと、もしくは、間接的にクライアントアプリケーションに利用させることが可能でしょう。
いずれにしても、遷移先がクライアントアプリケーション開発者の管理下にあれば、クライアントアプリケーションは認可レスポンスパラメーター群を受け取ることができます。
遷移先を事前に登録しておく
そういうわけで、クライアントアプリケーション開発者は、認可レスポンスパラメーター群を受け取る場所となる遷移先を、事前に認可サーバーに登録しておきます。この、事前に登録する遷移先のことを『リダイレクト URI』と呼びます。
たとえば、クライアントアプリケーションがリダイレクト URI として https://client.com/callback
を事前登録しておけば、認可レスポンスは次のようになるでしょう。
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
リクエストパラメーターを指定した場合は、トークンリクエストにおいても、同じ値を持つ redirect_uri
リクエストパラメーターを指定する必要があるので、注意してください。
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
の場合、
それは認可コードフローですので、認可レスポンスパラメーター群はクエリー部に埋め込まれます。下記の認可レスポンスの例で、code=123&state=abc
が ?
の後に続くことに注目してください。
HTTP/1.1 302 Found
Location: https://client.com/callback?code=123&state=abc
一方、インプリシットフローでは、認可レスポンスパラメーター群は、フラグメント部に埋め込まれる約束になっています。フラグメント部とは、URL 内で #
に続く部分のことです。
認可リクエストの response_type
リクエストパラメーターの値が token
の場合、
それはインプリシットフローですので、認可レスポンスパラメーター群はフラグメント部に埋め込まれます。下記の認可レスポンスの例で、access_token=xyz&token_type=Bearer
が #
の後に続くことに注目してください。
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 を入力すると、
クエリー部の q=Q
は https://example.com/callback
に渡されますが、フラグメント部の f=F
は渡されません。下記の HTTP リクエストの例で、?q=Q
は含まれているけれども #f=F
は含まれていないことに注目してください。
GET /callback?q=Q HTTP/1.1
Host: example.com
このため、https://example.com/callback
で待ち受けるプログラムは、フラグメント部の情報を知ることはできません。
認可レスポンスパラメーター群は、認可コードフローではクエリー部に、インプリシットフローではフラグメント部に置かれますが、この些細な違いは、上記の Web ブラウザの動作を意識したものです。
JavaScript はフラグメント部にアクセスできる
インプリシットフローでは認可レスポンスパラメーター群がフラグメント部に埋め込まれますが、HTTP リクエストにフラグメント部が含まれないとすると、遷移先で待ち受けるプログラムは認可レスポンスパラメーター群を取得することはできません。これはどういうことでしょうか?
実は、インプリシットフローにおいては、遷移先で動くプログラムは、認可レスポンスパラメーター群を取得しようとはしません。かわりに、Web ブラウザに対して、JavaScript を含む HTML を送り返します。この JavaScript そのものがクライアントアプリケーションです。
HTTP/1.1 200 OK
Content-Type: text/html
<html>
......
<script src="client.js"></script>
</html>
JavaScript は、Web ブラウザに読み込まれると、Web ブラウザ内で動作します。そして、Web ブラウザ内で動作する JavaScript は、フラグメント部を含む現在の URL 情報にアクセスすることができます。このため、JavaScript アプリケーションであれば、フラグメント部に埋め込まれた認可レスポンスパラメーター群を取得できます。
ちなみに、URL のフラグメント部は次の JavaScript コードで取得できます。
// URL のフラグメント部を取得する。
var fragment = window.location.hash;
インプリシットフローの使い所
これまでの技術説明を踏まえると、インプリシットフローは、「Web ブラウザ内で動く JavaScript アプリケーションにアクセストークンを直接発行したいときに使う」ということが言えます。
クエリー部ではなくフラグメント部を使うことで、(1)Web ブラウザとリダイレクト URI 先との通信の中を(アクセストークンを含む)フラグメント部の情報が流れない、その結果、(2)リダイレクト URI 先の Web サーバーのログに(アクセストークンを含む)フラグメント部の情報が記録されることはない、という利点が得られます。これにより、アクセストークン漏洩のリスクを下げることができます。
インプリシットフロー非推奨
RFC 6749 の発行から年月がたった現在、Web ブラウザ内で動く JavaScript アプリケーションにおいてもインプリシットフローを使うべきではない(かわりに認可コードフローを使うべき)というのがベストプラクティスとなっています(OAuth 2.0 Security Best Current Practice)。
Torsten Lodderstedt 氏は "Why you should stop using the OAuth implicit grant!"(2018 年 11 月 9 日)という記事で、CORS(Cross-Origin Resource Sharing)が普及したことによりインプリシットフローを使う必要がなくなったと説明していますが、この件については @ritou さんの考察をご参照ください:『OAuth 2.0 の Implicit grant 終了のお知らせ』
カスタムスキームではじまるリダイレクト 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/1.1 302 Found
Location: myapp://myhost/{認可レスポンスパラメーター群}
この認可レスポンスを受け取った Web ブラウザは、Location
ヘッダーに書かれた URI を処理しようとしますが、そのスキームが myapp
という見慣れないものであるため、自分自身では処理できません。そこで、myapp
スキームではじまる URI を処理できる外部プログラムがシステム内に存在するかどうかを調べます。そして、もし存在すれば、その外部プログラムに処理を委譲します。
ここで、クライアントアプリケーションが myapp
スキームを処理できる外部プログラムとして自分自身を事前登録していたならば、Web ブラウザはクライアントアプリケーションに Location
ヘッダーの値を渡すことになります。このような手順を踏むことで、めでたくクライアントアプリケーションに認可レスポンスパラメーター群が渡ってきます。
カスタムスキームに反応する外部アプリケーションを登録する方法はシステム毎に異なります。例えば Android であれば、AndroidManifest.xml
に次のような設定を追加することでカスタムスキームに反応できるようになります。
<data android:scheme="myapp" android:host="myhost" />
カスタムスキームに反応する外部アプリケーションの登録方法については、各システムの技術文書を参照してください。
App-Claimed https
2018 年 10 月 24 日に承認された※『Financial-grade API - Part 1: Read-Only API Security Profile』の Implementer's Draft 第 2 版の「5.2.2. Authorization server」には、"shall require redirect URIs to use the https scheme" という要求事項があります。
※:Implementer’s Drafts of Three FAPI Specifications Approved (Oct. 24, 2018)
これはつまり、FAPI 対応の認可サーバーとやりとりするクライアントアプリケーションが用いるリダイレクト URI のスキームは、必ず https
でなければならないということです。裏を返すと、カスタムスキームは使えません。
カスタムスキームは使えないけれども、外部 Web サーバーを立てずにクライアント側だけでリダイレクト処理を完結させたい場合、BCP 212(OAuth 2.0 for Native Apps)の「7.2. Claimed "https" Scheme URI Redirection」で言及されている手法を使うことになります。
一部の OS では、アプリケーションの管理下にあるドメインをホスト部に用いた https://
で始まる URI を、特別に扱うことができます。そのような URI に出会うと、ブラウザはそのページを読み込むのではなく、その URI を引数として外部アプリケーションを起動します。この動作により、クライアントアプリケーションは、カスタムスキームを用いたときと同様に、リダイレクトエンドポイントの処理を自ら実装することができます。
この、『app-claimed https』機能ですが、全ての OS で使えるわけではないので注意してください。FAPI ワーキンググループのある方からの情報によると、iOS と Android では使えるものの、Windows Mobile、Windows デスクトップ、Mac OS では使えないそうです。
認可コード横取り攻撃
同じカスタムスキームに反応する外部アプリケーションが複数登録されている場合、認可リクエストを発行したクライアントアプリケーション以外のアプリケーションが認可レスポンスを受け取ってしまう可能性がでてきてしまいます。
実際、悪意のあるアプリケーションが、わざと他のアプリケーションが用いているカスタムスキームに反応するように自分自身を登録し、認可サーバーが発行した認可コードを横取りしてしまう攻撃(authorization code interception attack)が発生しました。悪意のあるアプリケーションは、横取りした認可コードを用いて認可サーバーのトークンエンドポイントにアクセスし、アクセストークンを取得することができてしまいました。
この攻撃に対する対抗策として策定されたのが、RFC 7636(Proof Key for Code Exchange by OAuth Public Clients)、通称 PKCE(ピクシー)と呼ばれる仕様です。
『認可コードフロー』かつ『カスタムスキーム』を用いるクライアントアプリケーションは、この PKCE の仕様に準拠して、認可リクエストに code_challenge
リクエストパラメーターをつけることが必須となります(仕様上必須ではないですがセキュリティー的観点から必須です)。もちろん認可サーバー側でも PKCE をサポートしておかなければなりません。
PKCE に準拠するためにクライアント側とサーバー側でやらなければならないことについては、『PKCE: 認可コード横取り攻撃対策のために OAuth サーバーとクライアントが実装すべきこと』という記事を参照してください。
response_mode リクエストパラメーター
認可レスポンスパラメーター群は、認可コードフローであればクエリー部に、インプリシットフローであればフラグメント部に置かれます。このデフォルトの動作を変更するリクエストパラメーターが response_mode
パラメーターです。
response_mode
に query
を指定すると認可レスポンスパラメーター群はクエリー部に、fragment
を指定するとフラグメント部に置かれるようになります。
ただし、インプリシットフローでは response_mode=query
を指定してはいけないことになっています。ですので、response_mode
リクエストパラメーターでできることは、せいぜい、認可コードフローの認可レスポンスパラメーター群をフラグメント部に置くように response_mode=fragment
と指定することぐらいです。
下記の例では、認可コード(code
)がフラグメント部(#
に続く部分)に埋め込まれています。
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 という仕様で定義されている form_post
は意義ある変化をもたらします。
これまでの説明では、認可レスポンスの HTTP ステータスは 302 でした。しかし、response_mode=form_post
というリクエストパラメーターを認可リクエストにつけると、認可レスポンスの HTTP ステータスコードは 200 となり、そのレスポンスボディーに HTML が含まれるようになります。
次の認可レスポンスの例は、当該仕様書から抜粋したものです。
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
に
javascript:document.forms[0].submit()
と書かれていることです。これにより、Web ブラウザがこの HTML を読み込んだあと、フォームが自動的に送信されます。
結果として、上記の認可レスポンスを受け取った Web ブラウザは、client.example.org
に対して次のような 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)』、通称 JARM という仕様があります。これは、最初の Public Review を経て 2018 年 10 月 22 日に投票締切日を迎えるという、かなり新しい仕様です。(この記事を書いているのが 2018 年 10 月 22 日)
この仕様は、response_mode
リクエストパラメーターの値として、次の 4 つを新たに定義します。
query.jwt
fragment.jwt
form_post.jwt
jwt
これらのうちどれかを指定すると、認可レスポンスのレスポンスパラメーター群は一つの JWT(RFC 7519)にまとめられて、response
というレスポンスパラメーターとして返されます。
たとえば認可コードフローであれば、これまでは
HTTP/1.1 302 Found
Location: https://client.com/callback?code=123&state=abc
というように、レスポンスパラメーター群が個別に code
や state
といった名前を持って返却されてきていたところ、認可リクエストに response_mode=query.jwt
と付けると、次のような形で認可レスポンスが返ってきます。
HTTP/1.1 302 Found
Location: https://client.com/callback?response={JWT}
response
の値をデコードすると、ペイロード部に code
や state
といった、馴染みのあるレスポンスパラメーターを見つけることができます。
下記は、当該仕様書から抜粋した response_mode=query.jwt
に対する認可レスポンスの例です。
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 をデコードすると、ペイロード部が次の内容を持つことがわかります。
{
"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」で説明されています。
JARM 関連のメタデータ
JARM 仕様は、クライアントのメタデータとして次の 3 つを追加します(5. Client Metadata)。これらは、認可レスポンスパラメーター群を JWT にまとめるときに、その JWT の署名・暗号にどのアルゴリズムを使用してほしいかを指定するものです。
authorization_signed_response_alg
authorization_encrypted_response_alg
authorization_encrypted_response_enc
認可サーバーのメタデータとしては次の 3 つが追加されます(6. Authorization Server Metadata)。これらは、認可レスポンスパラメーター群を JWT にまとめるときに、その JWT の署名・暗号アルゴリズムとしてサーバーがサポートするアルゴリズムを列挙するものです。
authorization_signing_alg_values_supported
authorization_encryption_alg_values_supported
authorization_encryption_enc_values_supported
JARM をサポートする認可サーバーのディスカバリーエンドポイントからの応答には、これらのパラメーター群が含まれることになります。加えて、response_modes_supported
の値として、新たに query.jwt
、fragment.jwt
、form_post.jwt
、jwt
が追加されることになります。
JARM の実装状況
出来たばかりの仕様ですが、Authlete 社は 2018 年 10 月 2 日時点で実装を終わらせています。もしかすると、世界で最初の実装かもしれません。
現在 Public Review 期間中の仕様、『Financial-grade API: JWT Secured Authorization Response Mode for OAuth 2.0 (JARM)』、実装を終えました。response_mode=form_post.jwt も動きました。スクリーンショットは、Authlete が開発用に提供しているリダイレクトエンドポイントが表示した画面。 pic.twitter.com/Ihsx11Kpmg
— Takahiko Kawasaki (@darutk) 2018年10月2日
JARM の機能は Authlete 1.x 系の共用サーバー(api.authlete.com
)では利用できませんが、Authlete 2.1 以降では利用可能です。詳細は support@authlete.com までお問い合わせください。
注:本記事の執筆者(私)は Authlete 社の創業者です。
おわりに
認可レスポンスによるリダイレクト指示を受けたあと、Web ブラウザやリダイレクト URI 先のプログラム、クライアントアプリケーションがどのような動作をするのかについては、OAuth 2.0 の仕様範囲外ということもあり、詳細な解説記事をこれまで書いてきませんでした。
しかし最近、認可コードフローとインプリシットフローの違いを尋ねられることが増え、そのたびに、本記事で説明した内容をホワイトボードを使いながら口頭で説明している自分がいることに気がつきました。
これはもうやってられない、と。
このような背景のもと、本記事を書きました。書いてみて気付いたのは、「この分量の内容を口頭で説明されても消化しきれるわけない」ということでした。これまで私の説明を口頭で受けてきた方々、すみませんでした。今後は対面質疑応答ミーティングに先立ち、この記事を紹介させていただこうと思います。