CTP版 がリリースされてから1年が経過して全然本リリースの気配が見えない WIF Extension for OAuth ですが今更ながらちゃんと触ってみました。
(前回はサンプルプロジェクトを使って動きを少し解説しましたが、今回はスクラッチで作ってみます)
で、今回はせっかくなので OAuth2.0 がベースとなっている OpenID Connect の RP を WIF Extenstion で作ってみます。
(ちょっと微妙ですが Google の OP / Authorization Code Flow でテストしてみます)
また、WIF Extension がカバーしている範囲が access_token を取得するところまでなので、Userinfo Endpoint へのアクセスは手動です。OAuth の Protected Resource へのアクセスと全く同じです。。
尚、OAuth や OpenID Connect そのものの仕様に関しては私も人に説明するほど理解していないので
http://oauth.jp/
とか
http://d.hatena.ne.jp/ritou/
とか
http://www.sakimura.org/
なんかを参考にしてください。
■はじめに
まず始める前に、今回の流れと WIF Extension for OAuth のカバー範囲を解説します。
まず、やることですが「Googleアカウントに保持しているアカウント情報をユーザ(オーナー)同意の元、Webアプリケーションに代理取得させる」という仕組みを作ってみます。
処理の流れ、および WIF Extension for OAuth がやってくれる範囲は以下の通りです。
次に、WIF Extension for OAuth の仕組みですが、カスタムハンドラを定義することにより AuthZ Code や access_token の要求・受信に関するイベントを拾ってアプリケーションへ渡す、という形になっています。
■事前準備
事前準備として Google 側にこれから作るアプリケーションを登録して client_id および client_secret を取得しておく必要があります。
Google API コンソールへアクセスし、API Access メニューからアプリケーションを登録します。
今回作成するアプリケーションの URL を http://rp.adfs20.net/oauth_client/ とする予定なので、Web アプリケーションとしてその URL を登録すればよいのですが、一つ注意点です。
先のダイアログに書いたように実際に AuthZ Server や Resource Server からの通信を受け付けるのはカスタムハンドラなので、カスタムハンドラの URL が End Point として登録すべき URL になります。
(今回は http://rp.adfs20.net/oauth_client/OAuthHandler.ashx を登録します)
登録が終わると client_id および client_secret が表示されるので、保存しておきます。
■実装~テスト
では、さっそく実装してみます。
1.Visual Studio で ASP.NET 空のアプリケーションを作成する
まずは Visual Studio で ASP.NET のアプリケーションを作ります。
作成したら、プロジェクトに以下を新規追加します。
・Web フォーム( Default.aspx とします)
・グローバル アプリケーション クラス( Global.asax )
2.WIF Extension for OAuth のライブラリを参照する
ライブラリは以下の URL からダウンロード出来るのでダウンロードおよび解凍をしておきます。
ダウンロード URL :
https://connect.microsoft.com/site1168/Downloads
解凍したディレクトリにある「 Microsoft.IdentityModel.Protocol.OAuth.dll 」を参照します。
3.実装
各モジュールに以下のコードを実装します。
・web.config
ここにはカスタムハンドラの設定を記載します。
名称:OAuthHandler
パス:OAuthHandler.ashx (先に Google API Console に登録したパスと同じである必要があります)
このパスに対する HTTP 要求があった場合に WIF が動く、という具合です。
<system.webServer> <handlers> <add name="OAuthHandler" verb="*" path="OAuthHandler.ashx" type="Microsoft.IdentityModel.Protocols.OAuth.Client.EndUserAuthorizationResponseHandler, Microsoft.IdentityModel.Protocols.OAuth" /> </handlers> </system.webServer>
・global.asax
ここでは、OAuthClient の設定およびカスタムハンドラ内のロジック実装を行います。
まずは、AuthZ Server の登録ですが、Google OP の AuthZ Server のエンドポイントは
・Authorization : https://accounts.google.com/o/oauth2/auth
・Token : https://accounts.google.com/o/oauth2/token
ですので、あらかじめ取得しておいた client_id および client_secret と合わせて設定をしておきます。
// AuthZ Server登録 InMemoryAuthorizationServerRegistry serverRegistry = new InMemoryAuthorizationServerRegistry(); // For Google AuthorizationServerRegistration registrationInfo = new AuthorizationServerRegistration( new Uri("https://accounts.google.com/o/oauth2/token"), new Uri("https://accounts.google.com/o/oauth2/auth"), [取得した client_id], [取得した client_secret); serverRegistry.AddOrUpdate(registrationInfo); OAuthClientSettings.AuthorizationServerRegistry = serverRegistry;
次に、Resource Scope Mapping の登録です。
ここでは、指定した Scope パラメータに応じた権限を持つ access_token を発行するためのエンドポイントおよび Scope パラメータそのものを指定します。Google の場合は Authorization と同じエンドポイントになるため、
・Token : https://accounts.google.com/o/oauth2/token
・Resource Mapping : https://accounts.google.com/o/oauth2/auth
を指定します。
また、OpenID Connect を利用するときの scope は通常「openid」を指定しますが、Google の場合は
・https://www.googleapis.com/auth/userinfo.email
・https://www.googleapis.com/auth/userinfo.profile
を指定します。
// Resource Scope Mapping 登録 InMemoryResourceScopeMappingRegistry resourceRegistry = new InMemoryResourceScopeMappingRegistry(); // For Google resourceRegistry.AddOrUpdate( "token", new Uri("https://accounts.google.com/o/oauth2/token"), new Uri("https://accounts.google.com/o/oauth2/auth"), new string[] { "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile" }); OAuthClientSettings.ResourceScopeMappingRegistry = resourceRegistry;
global.asax 最後はイベントハンドラの登録とロジックの実装です。
WIF Extension for OAuth で拾える OAuth 関係のイベントは以下の4つです。
・access_token を要求(RequestingAccessToken)
・AuthZ code を受信(AuthorizationCodeReceived)
・access_token を受信(AccessTokenReceived)
・エンドユーザの同意(認可)が得られなかった(EndUserAuthorizationFailed)
それぞれについてハンドラの登録とメソッドの実装を行います。
まずはハンドラの登録です。
// Event Handler登録 OAuthClientSettings.RequestingAccessToken += new EventHandler<RequestingAccessTokenEventArgs>(OAuthClientSettings_RequestingAccessToken); OAuthClientSettings.AuthorizationCodeReceived += new EventHandler<AuthorizationCodeReceivedEventArgs>(OAuthClientSettings_AuthorizationCodeReceived); OAuthClientSettings.AccessTokenReceived += new EventHandler<AccessTokenReceivedEventArgs>(OAuthClientSettings_AccessTokenReceived); OAuthClientSettings.EndUserAuthorizationFailed += new EventHandler<EndUserAuthorizationFailedEventArgs>(OAuthClientSettings_EndUserAuthorizationFailed);
次にそれぞれメソッドを実装します。
今回は AuthZ code および access_token を受信したら Session に取得したデータを保持することします。
(RequestingAccessToken 及び EndUserAuthorizationFailed については何も実装せず空のメソッドを作ります)
それぞれイベントが発生するとイベントの引数(**EventArgs)に受信したトークンなどが入ってくるので取り出して Session へ Add します。
// アクセストークン要求イベント void OAuthClientSettings_RequestingAccessToken(object sender, RequestingAccessTokenEventArgs requestingTokenEventArgs) { } // AuthZ code 受信イベント public void OAuthClientSettings_AuthorizationCodeReceived(object Sender, AuthorizationCodeReceivedEventArgs codeReceivedEventArgs) { string code = codeReceivedEventArgs.AuthorizationCode.ToString(); codeReceivedEventArgs.HttpContext.Session.Add("code", code); } // アクセストークン受信イベント public void OAuthClientSettings_AccessTokenReceived(object Sender,AccessTokenReceivedEventArgs tokenReceivedEventArgs) { string accessToken = tokenReceivedEventArgs.AuthorizationResponse.Parameters[OAuthConstants.AccessToken]; tokenReceivedEventArgs.HttpContext.Session.Add("at", accessToken); string TokenType = tokenReceivedEventArgs.AuthorizationResponse.Parameters[OAuthConstants.TokenType]; tokenReceivedEventArgs.HttpContext.Session.Add("tokenType", TokenType); string idToken = tokenReceivedEventArgs.AuthorizationResponse.Parameters["id_token"]; tokenReceivedEventArgs.HttpContext.Session.Add("idToken", idToken); } // 認可失敗イベント void OAuthClientSettings_EndUserAuthorizationFailed(object Sender,EndUserAuthorizationFailedEventArgs authorizationFailedEventArgs) { }
これで global.asax は終わりです。次は実際のロジックを実装する部分(Default.aspx)です。
・Default.aspx
ここは非常にシンプルで、WIF Extension for OAuth のメソッドをコールするだけです。
コールするのは、OAuthClient.RedirectToEndUserEndpoint というメソッドです。
引数は、
・リソース名(global.asax の Resource Scope Mapping で指定したリソース名)
・Authorization Response Type : Authorization Code Flow の場合は Code を指定
・Redirect URL : リダイレクト先の URL。Google API 側に設定した URL と同じもの
です。
OAuthClient.RedirectToEndUserEndpoint( "token", AuthorizationResponseType.Code, new Uri("http://rp.adfs20.net/oauth_client/OAuthHandler.ashx"));
4.デプロイ
実装が終わったらビルドしてデプロイします。
IIS側の設定としては特に気を付ける必要はありませんが、.NET Framework 4.0 で動きますのでアプリケーションプールは .NET Framework 4.0 で動かしている物を割り当てる必要があります。
5.実行
さて、実行してみましょう。
作成した Web アプリケーションへアクセスします。
access token を取得しようとするとますは認証が走ります。
認証が終わるとリソースへのアクセスへの同意を求められます。
同意すると、
・AuthZ Code
・access_token
・token type
・id token
が取得できているのがわかります。
この状態で Userinfo エンドポイントに access_token を渡してやります。
エンドポイントアドレスは「https://www.googleapis.com/oauth2/v1/userinfo」ですから、ここへ HTTP GET で access_token を渡してやります。
Response.Redirect("https://www.googleapis.com/oauth2/v1/userinfo?access_token=" + Session["at"] as string);
すると、json 形式でユーザの情報が表示されます。
■終わりに
いかがでしたでしょうか?
元々非常にシンプルな実装を目指している仕様だけあって本当は WIF の様なライブラリは不要なのかも知れませんが、WebRequest などを自分で実装する手間は省けるのでコーディング量は減らせるかも知れません。
今後の認証プロトコルの本命は OpenID Connect になるとも言われているのでまずは仕組みを勉強するきっかけになれば良いかな?と思います。