読者です 読者をやめる 読者になる 読者になる

mikanmarusanのブログ

テクノロジーとかダイビングとか

YConnect(Yahoo! JAPAN)への認証をする「omniauth-yahoojp」を作ってみた

omnauth-yahoojpとは

railsとか各種ID(facebook, twitter, mixi, Googleなどなど)の認証機能を追加してくれるomniauthのYConnect(Yahoo! JAPAN)版を突貫で作ってみました(テストとか適当)。Github で公開しています。

omniauth-yahoojp - Github

ベースはomnioauth-oauth2で、Strategyという形でYConnectの機能を追加し、YConnectを最低限に利用できるようにしています。

git clone してもらい、gem build & gem install すれば利用できますが、自分でもだんだん面倒くさくなってきたのもあって、勢いで rubygems にもあげてます。

omniauth-yahoojp(0.1.0) - rubygems.org

gem install omniauth-yahoojp

ominiauth-yahoojpは、YConnect(OAuth2.0 + OpenID Connect)で認証・認可をした後に、UserInfoAPIにアクセスして、(なけなしの)ユーザー属性情報を取得する処理を簡単にします(これしかできないという)

事の発端

Yahoo! JAPANで開催されたOpen Hack Day Japan(2013/2/16〜2/18)の前哨戦、Open Hack Day Conference(2013/2/9)でYahoo! JAPANの代表としてYConnectの話をしました。

会場にて @ 氏から、Rubyのライブラリないの?なんて話になり、おまけにtwitterでは下記のようにいつのまにかレースになっており(出来レース感もあるけど)とりあえず作ってみました(githubで公開中)

使い方

sinatraとかもありますが(よくわからないので)今回は、Ruby on Railsでの使い方を例示します。

1. Gemfileに追記する
gem 'omniauth-yahoojp'
2. Gemfileに基づいて必要なgemをインストールする
bundle install
3. config/initializers/omniauth.rb

config/initializers/omniauth.rbに以下を記載します。これは他のStrategy(facebook, twitter, mixi, Googleなどなど)でも同じ書き方です。

Rails.application.config.middleware.use OmniAuth::Builder do
        provider :yahoojp, ENV['YAHOOJP_KEY'], ENV['YAHOOJP_SECRET'], 
        {
                :scope => 'openid profile email address'
        }
end

client_idやclient_secretを直接記載しても良いですが、エレガントさに欠けるのでここは環境変数からとるようにします。

第4パラメータは Authorization エンドポイントで利用するパラメータです。UserInfoAPI で取得したい属性の scope を指定するほか、ログイン画面や同意画面の挙動を変更する :display, :prompt も指定できます。

4. config/routes.rb

Railsの中からYConnectで認証をする場合は、通常YConnectのAuthorization Codeエンドポイントにリダイレクトさせるのですが、omniauth-yahoojpではomniauth-yahoojpが提供している /auth/yahoojp に下記のような方法で飛ばします。

viewだとこんな感じ

<%= link_to "Yahoo! JAPAN でlogin", "/auth/yahoojp" %>

controllerだとこんな感じ

redirect_to /auth/yahoojp

YConnectから戻ってくる時は、 /auth/yahoojp/callback に自動的に戻ってくるようになっているので、これをコントローラにマッピングするために config/routes.rb に以下を記載します。

match '/top' => 'sessions#top' # toppage
match '/auth/:provider/callback' => 'sessions#callback' #戻り先

これで SessionController クラスの callback メソッドが呼ばれるようになります。/top はなんとなくスタートページ(YConnectへのキック元)としました。

5. SessionControllerを記述する
rails g controller sessions

で SessionControllerクラス を作り、下記を記載します。

class SessionsController < ApplicationController
        def top
                render 'sessions/top'
        end

        def callback
                auth = request.env['omniauth.auth']

                @user_id = auth.uid

                @name  = auth.info.name
                @email = auth.info.email
                @first_name = auth.info.first_name
                @last_name  = auth.info.last_name

                @token         = auth.credentials.token;
                @refresh_token = auth.credentials.refresh_token;
                @expires_at    = auth.credentials.expires_at;

                render 'sessions/callback'
        end
end

認証結果は、 request.env['omniauth.auth'] の中に入ってきます。uid()でuser_idを、info()でUserInfoAPIで得られる属性が取得できます。

6. viewを記載

SessionsControllerにおいてrenderで指定したHTMLファイル(erb)を設置します。

app/views/sessions/top.html.erb

<h1>Hello! YConnect (Authorization Code Grant Flow)</h1>
<div id="user_nav">
<%= link_to "Yahoo! JAPAN でlogin", "/auth/yahoojp" %>
</div>

app/views/sessions/callback.html.erb

<div id="user">
  <p>Hello, <%= @name %> さん</p>
  <div id="credentials">
 Credentials:
 <ul>
   <li>access_token: <%= @token %></li>
   <li>refresh_token: <%= @refresh_token %></li>
   <li>expires_at: <%= @expires_at %></li>
 </ul>
  </div>
  <div id="userinfo">
 UserInfo:
 <ul>
   <li>user_id:    <%= @user_id %></li>
   <li>email:      <%= @email %></li>
   <li>name:       <%= @name %></li>
   <li>first_name: <%= @first_name %></li>
   <li>last_name:  <%= @last_name %></li>
 </ul>
  </div>
</div>
7. やっと起動
rails server

思うこと

omniauth-oauth2は、内部的にrubygemのoauth2を使っているせいか、RFC6749RFC6750に完全に対応していないようです(コードのコメントではドラフト15になってますね)。

Authorization Code Grant Flow の token取得でBasic認証周りがつらい感じになっていたり、そもそもOpenID Connectに対応していないっぽいのでid_tokenが利用できない雰囲気に見えました。本当はできるのかな。。。

その他

rubyよりもpythonの方がかっこいいと思い(偏見)、rubyを全然やったこなかったので(言い訳)、全然なれていない感じで個人的にはだめだめな印象でございますので、なにかありましたらGitHubへpull requestください。

※あ、個人の創作物なので、所属組織とは関係ないよ!

ついでにとあるエンジニアのメモランダムというfacebookページもありますので、いいね!お願いします(宣伝)