2024年5月20日月曜日

response_mode、”form_post"の実装

こんにちは、富士榮です。

EntraをはじめとするMicrosoftのIDスタックを使って開発をしていると必ずといっていいほど出てくるのがreponse_mode=form_postの壁です。特に非Microsoftのライブラリを使ってリライングパーティを開発する場合や、MicrosoftのIDスタックをリライングパーティとして非MicrosoftのIDaaSを構築する場合にはこのパラメータへの対応の有無で悩むことになります。

ということでresponse_modeって何?と言う話と自前でIdPを構築する際にform_postを実装する場合はどんな実装になるのか?を解説してみます。

response_modeとは?
OpenID Connect 1.0の仕様にはこのように定義されています。
OPTIONAL. Informs the Authorization Server of the mechanism to be used for returning parameters from the Authorization Endpoint. This use of this parameter is NOT RECOMMENDED when the Response Mode that would be requested is the default mode specified for the Response Type.

まぁ、元々はOAuth2.0のレスポンスの定義なので、こちらの仕様をみる方が適切といえそうです。

https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes

OPTIONAL. Informs the Authorization Server of the mechanism to be used for returning Authorization Response parameters from the Authorization Endpoint. This use of this parameter is NOT RECOMMENDED with a value that specifies the same Response Mode as the default Response Mode for the Response Type used. 

このOAuth2.0の仕様が発行されたのが2014年なのでその時点ではqueryとfragmentの2種類だけが本文には記載されており、追加モードとして今回の主役であるform_postの定義への参照が記載されています。


と、ここでちょっと待て、、と。

上記を見るとresponse_typeのデフォルトのresponse_modeの値を取る場合以外はこのパラメータを使うことは推奨しない、と明確に書いてありますね。

ということは、Azure AD B2Cが外部IdPと連携する際にresponse_typeの値に関わらずresponse_modeを必ず指定するのは推奨外の動作ってことですよね。。

また、先日書いたEntra IDの外部認証プロバイダ連携の場合はresponse_type=id_tokenでリクエストがされ、かつ外部認証プロバイダからはform_postでレスポンスが返されることを期待するにも関わらずresponse_modeが指定されないのはどうなんだ、、、という話です。(というか世の中にあるIdP製品やサービスでresponse_type=id_tokenだけをリクエストされてform_postでレスポンスするものは存在しないと思います)

この辺はMicrosoftさんちゃんと仕様を見ようよ・・・って思いますね。。

ちなみにresponse_type=id_tokenの場合のデフォルトのresponse_modeはfragmentです。(こちらに定義されています)

https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#id_token

The default Response Mode for this Response Type is the fragment encoding and the query encoding MUST NOT be used.


form_postとは?

気を取り直してresponse_mode=form_postの話に戻ります。response_modeは認可サーバからの認可レスポンスを返却するための方式を示すパラメータであることはわかりましたが、値にform_postが指定するというのはどう言うことなのか、についてはこちらの追加仕様に記載されています。

https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html

Abstractにこう記載されています。

This specification defines the Form Post Response Mode. In this mode, Authorization Response parameters are encoded as HTML form values that are auto-submitted in the User Agent, and thus are transmitted via the HTTP POST method to the Client, with the result parameters being encoded in the body using the application/x-www-form-urlencoded format.

まぁ、要するにHTML formにresponse情報を入れてredirect_uriにPOSTしますよ、ってことです。ws-federationやSAMLのHTTP POST Bindingですね。

ここでも話は横にそれますが、今でこそOAuth2.0やOpenID Connectは認可コードをリライングパーティに発行し、リライングパーティは認可サーバのTokenエンドポイントへ投げ込んでaccess_tokenやid_tokenを受け取る、いわゆるresponse_type=codeのコードフローが主流になっていますが、SAMLも当初はHTTP POSTやRedirect BindingではなくArtifact Bindingが主に使われていた時代がありました。当時はフィーチャーフォンのブラウザなど扱えるURL長に制限があったり、フロントに大きなPOSTデータを持ってくると通信量が増えてパフォーマンスに大きく影響が出るなどの問題があり、ArtifactといわれるコードをService Providerへ提供、Service ProviderがIdentity ProviderへSAMLトークンをとりにいく、という流れが必要だったためです。当時と今では事情がことなりますが、認可コードフローとのままですね。なんといってもOpenID Connectは、開発中はOpenID ABC(Artifact Binding and Connect)って名前でしたし、OpenID Connect Coreの定義をしている、OpenID FoundationのAB/ConnectワーキンググループはArtifact Binding Working GroupとConnect Working Groupから組成されており、ABはArtifact Bindingなわけです。


話を戻すと、Entra IDの外部認証プロバイダの際にも書いた通り、id_tokenとstateをformに乗せてPOSTしてあげるためのHTMLをレンダリングするコードを書けば良いってことになります。

node_expressとejsで書くとこんな感じです。

res.render("./form_post.ejs",
{
redirect_uri : req.body.redirect_uri,
id_token: id_token,
state: req.body.state
}
);

レンダリングされるhtmlテンプレートはこんな感じですね。(前回の記事では動きを見るために自動POSTしていませんでしたが、今回はJavaScriptで自動POSTする形にしています)

<html>
<head><title>Submit This Form</title></head>
<body onload="javascript:document.forms[0].submit()">
<form method="POST" action="<%= redirect_uri%>">
<input name="id_token" type="hidden" value="<%= id_token%>">
<input name="state" type="hidden" value="<%= state%>">
</form>
</body>
</html>


なぜform_postにこだわるのか?

しかし、form_postを使っているのって実態としてMicrosoftくらいしかいないんですよね・・・(Sign In with Appleも使ってたかも)

現状、ブラウザを経由してトークンのやり取りをする、つまりArtifactや認可コードを使わないパターンを使いたい大きな理由は先ほど挙げたフィーチャーフォンのブラウザの制限や通信速度・通信量の問題というよりも、エンタープライズなどIdPがファイアウォールの内側にありリライングパーティが外側にあるというケースやWalletがIdPになるケースなど、IdPが外部からのアクセスできない(Walletの場合はエンドポイントを持たない)という問題である場合が多いと思います。

その場合は当然Implicitを使う、つまりフラグメントにトークンを入れてJSでリライングパーティへ渡す、というやり方になるわけですが、これだとリライングパーティのredirect_uriがブラウザ上でJSをハンドリングする機能を実装することが前提になってしまいます。もちろんこれができるケースならば良いのですが、リライングパーティ向けの共通ライブラリを提供しようとすると単純にバックエンドでPOSTを受け取るAPIを作っておく方が楽なんじゃないかと思います。さらに言うと、これまでws-federationやSAMLのSP向けのライブラリを提供してきたベンダであれば少しパラメータを変えれば対応できると言う意味でform_postの方がありがたかった、と言うのが実情だったんだろうなぁ、、と聞いた話から推測しています(あくまで推測です)。まぁ、SameSiteの問題もあったりしますがこの辺は各社対応してきているので現状は大丈夫だと思いますが。


ということで、今回はresponse_modeとform_postの話をしましたが、みなさんが作るIDシステムやアプリケーションのシステム配置や要件によって適切な実装をしていきましょう。








0 件のコメント: