OpenIDでRPから強制的にPW再確認させる方法

こんばんは、ritouです。
最近はOpenID Connectばっかりなので、たまには現役のOpenID Auth 2.0の話をしましょう。

今日の内容は、「OpenIDでRPが必要な時にPW再確認をさせる方法」です。
Yahoo!(米)はGoogle/Facebookのアカウントを受け入れていますが、昔からアカウント情報の修正などの処理時には毎度PW再確認による本人確認を行ってきました。外部のOpenIDやアカウントを受け入れた状態で同じレベルの処理を行うために、RPからOPに強制的にPW再確認をしてもらうように要求できなければなりません。

では、それってどう実装するの?ってことで新しい仕様ではないのですが、動作確認を交えて紹介します。

PAPEという拡張のmax_auth_ageというパラメータを使えばいい

結論から行きます。

PAPE拡張を使います。
Final: OpenID Provider Authentication Policy Extension 1.0

PAPEというとマルチファクターとか、なんだか難しそうな仕様に感じる方もいるかと思いますが、今回の話はシンプルです。

対象はこちら。

・ openid.pape.max_auth_age
(Optional) If the End User has not actively authenticated to the OP within the number of seconds specified in a manner fitting the requested policies, the OP SHOULD authenticate the End User for this request using the requested policies. The OP MUST actively authenticate the user and not rely on a browser cookie from a previous authentication.

Value: Integer value greater than or equal to zero in seconds.

If an OP does not satisfy a request for timely authentication, the RP may decide not to grant the End User access to the services provided by the RP. If this parameter is absent from the request, the OP should authenticate the user at its own discretion.

ざっくり言うと、RPは、「ここで指定した秒数以内に認証したユーザーをください」と指定します。
OPは、現在のログイン状態を確認して、その秒数より前に認証したユーザーであれば再認証としてPW再確認などを行います。

このパラメータに0を指定すると必ず再確認してくれそうです。

Google, Yahoo!(米)はこのPAPE拡張に対応しているようなので試してみます。
Yahoo! JAPANmixiは対応していないようです。

Yahoo!(米)で試してみる

まずは、ログイン状態でOpenIDの普通のフローを試してみます。
Yahoo!にログインしていない状態でこのようなリクエストを送ります。

Yahoo! TEST URL1

https://open.login.yahooapis.com/openid/op/auth
?openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select
&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select
&openid.mode=checkid_setup
&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0
&openid.realm=http%3A%2F%2Fr-weblife.sakura.ne.jp%2F
&openid.return_to=http%3A%2F%2Fr-weblife.sakura.ne.jp%2Fphp-openid-popup-example%2Ffinish_auth.php

ここでは、ログイン画面、同意画面と画面が続き、RPに戻ります。

(あれれ、私ログインシール使ってないですね。)

もう一度、先ほどのURLにアクセスします。
ここで、ユーザーはYahoo!にログイン済なので、OP側のYahoo!では何も画面を出さずにRPに戻ります。

次に、PAPEのmax_auth_ageのパラメータを指定します。

Yahoo! TEST URL2

https://open.login.yahooapis.com/openid/op/auth
?openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select
&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select
&openid.mode=checkid_setup
&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0
&openid.realm=http%3A%2F%2Fr-weblife.sakura.ne.jp%2F
&openid.return_to=http%3A%2F%2Fr-weblife.sakura.ne.jp%2Fphp-openid-popup-example%2Ffinish_auth.php
&openid.pape.max_auth_age=0
&openid.ns.pape=http%3A%2F%2Fspecs.openid.net%2Fextensions%2Fpape%2F1.0

PAPEのmax_auth_ageの対象部分は後ろの2つのパラメータです。
このURLにアクセスすると、PW再確認画面が表示されます。

これで、RPが重要な処理を行う際やRPが認識しているログイン後時間がたっているときなど、OPに再認証を依頼することができます。

Googleで試してみる

Googleでも同じ挙動が確認できます。

Google TEST URL1

https://www.google.com/accounts/o8/ud
?openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select
&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select
&openid.mode=checkid_setup
&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0
&openid.realm=http%3A%2F%2Fr-weblife.sakura.ne.jp%2F
&openid.return_to=http%3A%2F%2Fr-weblife.sakura.ne.jp%2Fphp-openid-popup-example%2Ffinish_auth.php

Google TEST URL2

https://www.google.com/accounts/o8/ud
?openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select
&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select
&openid.mode=checkid_setup
&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0
&openid.realm=http%3A%2F%2Fr-weblife.sakura.ne.jp%2F
&openid.return_to=http%3A%2F%2Fr-weblife.sakura.ne.jp%2Fphp-openid-popup-example%2Ffinish_auth.php
&openid.pape.max_auth_age=0
&openid.ns.pape=http%3A%2F%2Fspecs.openid.net%2Fextensions%2Fpape%2F1.0

PAPEって難しそうに感じるかもしれませんが、個々のパラメータを見ていくと面白げなパラメータがあります。
ぜひみなさんもチェックしてみてくださ・・・え?今年はOpenID Connectが来るっていってるお前が何言ってるのかって?

OpenID Connectではどうなるのか?

OpenID Connectでも、同様のリクエストを送ることが可能です。
下記JSONオブジェクトの、id_tokenの方にmax_ageとあるのがそれです。

{
 "userinfo":
   {
     "claims":
       {
         "name": null,
         "nickname": {"optional": true},
         "email": null,
         "verified": null,
         "picture": {"optional": true},
       },
     "format": "signed"
   }
 "id_token":
   {
     "claims":
       {
        "auth_time": null
       }
     "max_age": 86400,
     "iso29115": "2"
   }
}

この辺の解説はまたあとで。
ではまた。