OpenID Connectを実装するためのOAuth 2.0拡張

こんばんは、ritouです。

7/8にmixiで行われたidcon #9でOpenID Connectの仕様を紹介させていただきました。
The Latest Specs of OpenID Connect at #idcon 9
私の力不足により、OpenID Connectを「よくわからん」「難しそうだ」と思われたかもしれませんので、ブログで少しずつリベンジしていこうかと思います。

idconではいくつかの機能を紹介しました。
今回は、"OpenID Connect HTTP Redirect Binding"の仕様に書かれている、"OAuth 2.0への追加実装"について整理します。

OpenID Connect HTTP Redirect Binding

Spec obsoleted by openid-connect-standard-1_0

"OpenID Connect HTTP Redirect Binding"には、OAuth 2.0のProvider/Clientが下記の機能を実装する方法が中心です。

  • UserInfo Endpoint
  • Session Management

OAuth 2.0でユーザーのパスワードを扱わないAuthZ Code FlowとImplicit Flowについて、OAuth 2.0のProvider/Clientが何を実装すればいいかを説明します。
はじめに、OAuth 2.0 AuthZ Code Flowからです。

【AuthZ Code Flow】UserInfo Endpoint実装方法

OAuth 2.0のリソースアクセスとして、ClientはUserInfo EndpointにAccess Tokenを用いたリクエストを送ります。
Server側は、ユーザーの属性情報などのClaimsを返します。

ざっくりのシーケンス図はこちらです。

AuthZ Endpointへのリクエスト : Client(RP)

Clientは、次のようにAuthZ Endpointへのリクエストを拡張します。

  • 1. scopeパラメータに"openid"を含む
  • 2. Claimsの指定などを行う場合は、requestパラメータを含む

例はこちらです。

https://server.com/op/authorize?
response_type=code
&client_id=s6BhdRkqt3
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
&scope=openid
&state=af0ifjsldkj
&request=jwt_header.jwt_payload.jwt_crypto

2のrequestパラメータの中身はSession Managementと絡むのであとで説明します。

AuthZ Endpointの処理 : Server(OP)
  • scopeに"openid"が含まれていたら、ユーザーの同意のもとにUserInfo Endpointへのアクセス権限をClientに渡す
  • requestパラメータが含まれていた場合、UserInfo Endpointのレスポンスに含む値を管理しておく

基本的にはこれ以外、通常のOAuth 2.0のフローの通り、Access Tokenのやりとりが行われます。

ここで一点注意しなければいけないのは、AuthZ Request時にリソースアクセス時のレスポンスフォーマットについての指定が入ってくることです。
このあたりのしくみはServerの実装に依存しますが、Scopeパラメータの内容とClientの属性(パートナー企業かどうか、公式アプリかどうかなど)による制御などが行われているのではないかと思います。
属性にかかわる処理ですのでユーザーへの説明部分と、Access/Refresh Tokenと紐づけ方あたりは工夫が必要かもしれませんね。

UserInfo Endpointにアクセス : Client(RP)

リソースアクセス時には、Access Tokenと共に以下のパラメータを含みます。

  • schema パラメータに"openid"を指定する

既にProfile APIのようなEndpointを持っている場合、schemaパラメータが"openid"のときにOpenID Connectの仕様に従ったフォーマットでレスポンスを返せばいいのです。

このレスポンスの詳細については、これらの仕様を参照してください。

結局いろいろ省略しましたが、流れとしてはこれだけです。
そんなに難しくないですよね?

次にSession Managementの実装を説明します。

【AuthZ Code Flow】Session Management実装方法

OpenID ConnectのSession Management機能には、ID Tokenというものを利用します。
ID TokenにはユーザーIDやClientIDなどが含まれたJSONオブジェクトをトークンにしたものですが、説明すると長くなるのでこちらを参照してください。

ID TokenはAccess Tokenと同じタイミングで渡され、Client側はそれをOAuth 2.0にはないOpenID Connect独自のセッション管理に利用します。

AuthZ Endpointへのリクエスト : Client(RP)
  • 1. response_typeに"id_token"を追加("code id_token")
  • 2. PAPE関連など、id_tokenに関する細かい指定を行う場合は、requestパラメータを含む

2は先ほども出てきましたね。
このrequestパラメータというのは、UserInfo関連+セッション管理関連のリクエストパラメータを含みます。

AuthZ Endpointの処理 : Server(OP)
  • response_typeに"id_token"が含まれていたら、id_tokenを返す用意をしておく
  • requestパラメータが含まれていた場合、ID Tokenの作成時のために内容を管理しておく

このあと、Clientは通常のOAuth 2.0と同様にAccess Tokenを要求します。

Token Endpointの処理 : Server(OP)
  • Access Tokenと共にID Tokenを返す
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
    "access_token": "SlAV32hkKG",
    "token_type": "JWT",
    "refresh_token": "8xLOxBtZp8",
    "domain": "op.example.com",
    "expires_in": 3600,
    "id_token":"jwt_header.jwt_payload.jwt_crypto"
}

こんな感じですね。

ID Tokenを用いたセッション管理

ここから先は、OAuth 2.0の範囲を超えた新規エンドポイントの実装になりますので省略します。
気になった方はこちらの仕様を見てください。
Draft: OpenID Connect Session Management 1.0 - draft 28

このように、OAuth 2.0の機能で拡張しなければならない点はそれほど多くはありません。
もうだいぶ長くなってしまったので、Implicit Flowの方はざっくり説明します。

【Implicit Flow】

AuthZ Code Flowとの違いは以下の通りですね。

  • Access Tokenをすぐに渡す
  • Client Credentialsを含むToken EndpointへのAccess Token要求リクエストが存在しない

ということで、AuthZ Code Flowとの実装の違いは以下の通りです。

  • Client : AuthZ Endpointへのリクエストにresponse_typeに含まれるcodeがtokenに変わる
  • Server : Session ManagementのためのID TokenをAccess Tokenと同様にFragment Identifierとして渡す

ここまでで、2つの機能のためのOAuth 2.0の拡張について説明しました。
他に2つだけ説明します。

UserInfo/Session Managementのためのrequestパラメータの正体

は、次のようなJSONオブジェクトです。

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

最近、メンバー名が3文字フォーマットから読みやすい形に変わりました。
(※@_natさんがidconのフィードバックとしてMLに持っていき、検討していただいたようです。)
UserInfoに関する指定はuserinfoの下、Session Managementに関する指定はid_tokenの下にあります。

このJSONオブジェクトを、JWTによりToken形式に変換され、requestパラメータとなります。
詳しくはこちらの仕様をご覧ください

モバイル環境での実装に使える"request_uri"

OAuth 2.0では、AuthZ EndpointへのリクエストにURLなどを含みます。
さらにOpenID Connectのrequestパラメータが含まれると、さらにURL長は長くなり、日本のモバイル環境での利用に支障をきたす可能性が出てきます。
そこで、request_uriパラメータの利用が提案されています。
手順は以下の通りです。

  • 1. Clientは、AuthZ Endpointに送りたいリクエストパラメータをJSONオブジェクトとして用意
  • 2. Clientは、JSONオブジェクトを返すURLをrequest_uriパラメータとしてAuthZ Endpointへのリクエストに含む
  • 3. Serverは、request_uriにアクセスしてリクエストパラメータを取得

シーケンスは次のようになります。

このrequest_uriはClientのドメイン上になければならない制限はなく、場合によってはServer側がrequest_uriを登録できるしくみを用意するかもしれません。
極端な話、第3者が提供しても良いわけですね。
request_uriは下記のようなJSONオブジェクトを返します。

{
    "response_type": "code",
    "client_id": "s6BhdRkqt3",
    "redirect_uri": "https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb",
    "scope": "openid",
    "state": "af0ifjsldkj"
    "request": "jwt_header.jwt_payload.jwt_crypto"
}

こちらがrequest_uriパラメータを含むリクエストです。
他の拡張などでパラメータがどんどん増えても、JSONオブジェクト側に追加していくことになります。
このURL長は512バイト以内に制限があります。

https://server.com/op/authorize?
response_type=code
&client_id=s6BhdRkqt3
&request_uri=https://rp.example.com/rf.js%23Qfsoe2F
&state=af0ifjsldkj

これはOpenD ConnectというよりはOAuth 2.0の仕様に含まれてもよさそうな気がしますね。

まとめ

今回の内容により、「OAuth 2.0の機能のうち、OpenID Connectに関わり拡張が必要な部分」はそれほど多くないことがわかっていただけたでしょうか?
残りはConnect独自のエンドポイントの実装ですが、今後少しずつ説明していく予定です。

あ、重要なことを言うのを忘れていました。
HTTP Rediredt Bindingという名前、そのうち変わります。
ではまた!