OpenID ConnectでOpenID Auth 2.0ライクな動的UXを実現する方法

こんばんは、ritouです。
忘れてしまっている方もおられるかと思いますが、いわゆるOpenID(Authentication 2.0)を使って認証要求を送るまでに、次のような処理が行われます。

  • ユーザーがOP選択 or 識別子の入力
  • RPがDiscoveryを行い、OP Endpoint URLを取得
  • RP,OP間でAssociation確立

これらの処理が行われることで、RPからみると「各OPへの事前登録なんてものは不要」、ユーザーからみると「自分が使っているOPが利用できる」、OPからみると「仕様に沿って実装すればどのRPでも使える」という仕様になっているわけです。

さて、仕様策定が進められているOpenID Connectですが、何度も言ってるとおり、OAuth 2.0ベースです。
OAuthというと、あらかじめClientの事前登録(Client ID/Secretの取得)が必要で、現行のOpenIDとはモノが違うじゃないかと思われる方もいるのではないでしょうか?

そこで、現在検討されている仕様を紹介しながら、現行OpenIDと同様のUXをOpenID Connectで実現する方法を説明します。
ちなみに、今回の内容はOpenID Connectの仕様群において応用編といえる部分ですが、個人的には今後現行OpenIDとの比較がなされていく中でわりと重要な意味合いを持つ部分なのではないかと思っています。

1. ユーザー入力→OP特定→各Endpoint取得

OpenID Connectでは、ユーザーがOPを求めるための識別子を入力します。

  • xri
  • E-mail address
  • URL

例えば、私が"ritou@openidconnect.info"とかいうメールアドレスを入力したことにしましょう。
ここから、RPはSimple Web Discoveryを用いてOpenID Connect Issuerを取得します。

GET /.well-known/simple-web-discovery?
principal=ritou%40openidconnect%2Einfo&
service=http%3A%2F%2Fopenid%2Enet%2Fspecs%2Fconnect%2F1%2E0%2Fissuer
HTTP/1.1
Host: openidconnect.info

HTTP/1.1 200 O.K.
Content-Type: application/json

{
"locations":["https://openidconnect.info/"]
}

これで、なんとなくOPが"https://openidconnect.info/"だということがわかりました。
続いて、各エンドポイントなどの情報(Provider Configuration Information)を取得するために、"Provider Configuration Request"を送ります。

GET /.well-known/openid-configuration HTTP/1.1
Host: openidconnect.info

HTTP/1.1 200 O.K.
Content-Type: application/json

{"version":"3.0",
"issuer":"https://openidconnect.info/",
"authorization_endpoint":"https://openidconnect.info/connect/authorize",
"token_endpoint":"https://openidconnect.info/connect/token",
"user_info_endpoint":"https://openidconnect.info/connect/userinfo",
"check_session_endpoint":"https://openidconnect.info/connect/check_session",
"registration_endpoint":"https://openidconnect.info/connect/register",
"scopes_supported":["openid","PPID"],
"flows_supported":["code","token"],
"identifiers_supported":["public","ppid"]
}

ごっそりOPの情報が返ってきました。
今のこの部分が、OpenID Connectで使うDiscoveryとなります。仕様はこちらです。
http://openid.net/specs/openid-connect-discovery-1_0.html

2. Dynamic Client Registration

ここから、動的なClient登録になります。
RPはissuerに対してClient登録をしているかどうかを確認し、まだ登録していない場合は次のようなリクエストを送ります。
リクエストの送り先は、1で取得した"registration_endpoint"です。
Client(RP)名、redirect_uriを指定してみたり。

POST /connect/register HTTP/1.1
Accept: application/json
Host: openidconnect.info

{
"type": "client_associate",
"application_name":"Dynamic Client Registration Sample RP",
"redirect_uri": ["https://localhost/dummy/"]
}

HTTP/1.1 200 O.K.
Content-Type: application/json

{"client_id":"f3be4185d54e04989df3254fd2efa962487b47b0",
"client_secret":"0c14f2ba6264f9d2c1b76e721eb002e8aa0049e9",
"expires_in":3600
}

client_id,client_secretが返されます。

このあたりの仕様はこちらです。
http://openid.net/specs/openid-connect-registration-1_0.html

登録はこれで完了です。
この値をキャッシュしておいて、あとはOAuth 2.0ベースのフローに入っていくわけです。
例えばこんな感じですね。

https://openidconnect.info/connect/authorize
?response_type=code
&scope=openid
&client_id=f3be4185d54e04989df3254fd2efa962487b47b0
&state=sample_state_string

そんなに難しくなさそうですよね?実際に上記のリクエストを送ると動作を確認できますので興味がある方はやってみてください。

以上で事前登録なしの状態からOpenID Connectのリクエストを送るところまでの処理のざっくりな説明はおわりです。
OpenIDの説明で良く出てきたいわゆるユーザーセントリックなユースケースとして、こんな仕様があることを覚えておいていただければと思います。

そもそも、OAuthにおいて動的なClient登録の話は以前からあったのですがあまり積極的に議論されておらず、今回紹介した仕様も実用に耐えうるほどには固まっていません。
個人的にはブログのコメント欄とか、現行のOpenIDの適用範囲では導入しても良さそうな気がしています。
興味がある方は是非、エラーレスポンスなども決まってない部分があるので一緒に考えませんか?

と、ここまで書いておいて、OpenID Connect Sandbox(https://openidconnect.info/)の普通の使い方をまだ紹介していないことを思い出しました。またあとで書きます。
ではまた。