認証APIいろいろをRubyで使う

はてな認証API

http://auth.hatena.ne.jp/

Rubyはてな

Hatena::API::AuthRuby版をid:secondlifeさんが公開してくれてる。

gem install hatenaapiauth
require 'rubygems'
require 'hatena/api/auth'
require 'cgi'

cgi = CGI.new

params = {
  :api_key=>"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  :secret=>"xxxxxxxxxxxxxxxx"
}
auth = Hatena::API::Auth.new(params)
begin
  hatena = auth.login(cgi['cert'])
  # hatena = {
  #   "name" => "taslam",
  #   "image_url" => "http://www.hatena.ne.jp/users/ta/taslam/profile.gif",
  #   "thumbnail_url" => "http://www.hatena.ne.jp/users/ta/taslam/profile_s.gif"
  # }
rescue Hatena::API::AuthError => e
  # 認証失敗
  # auth.uri_to_login.to_s にリダイレクトではてなの認証ページに。
end

livedoor Auth

http://auth.livedoor.com/

Rubylivedoor Auth
gem install livedoorauth
require 'rubygems'
require 'livedoorauth'
require 'cgi'

params = {
  :app_key=>"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  :secret=>"xxxxxxxxxxxxxxxx"
}

cgi = CGI.new
auth = Livedoor::API::Auth.new(params)
begin
  user = auth.validate_response(cgi.query_string)
  # user = {
  #   :userhash => 'ユーザを識別するアプリ毎に一意な文字列',
  #   :token => 'LivedoorID取得に用いるtoken',
  #   :userdata => '認証URL生成時に指定した引継ぎデータ',
  # }

  # 認証成功
  # URL生成時に:perms => 'id'を指定していればLivedoorIDを取得可能。
  # livedoor_id = auth.get_livedoor_id(user)
rescue
  # 認証失敗
  # ログイン用URIインスタンス取得
  # Livedoorへのログイン、アプリケーションの認証情報利用の許可など
  # 認証できたらあらかじめ設定しているコールバックURLにリダイレクト要求発生
  # userdataはそのままコールバックURLに付加されて帰ってくる
  # :permsのデフォルトはuserhash。idにすると、LivedoorIDを取得できるようになる。
  login_uri = auth.uri_to_login(:userdata => 'data')
  # login_uri にリダイレクトで、Livedoorに認証ページに。
end

YahooBBAuth

http://developer.yahoo.com/auth/
Yahoo!Japanのほうは、現在のところ一般公開はしていない模様(´・ω・`)

Google

GoogleAccountAuthenticationを使う。
http://code.google.com/apis/accounts/AuthForWebApps.html

RubyでGoogleAccountAuthentication

GoogleCalendarサービスでの認証を利用する。
今回はライブラリがあったので、利用させていただいた。

gem install gcalapi
begin
  GoogleCalendar::Service.new('taslam@example.com', 'password')
  # カレンダー取得メソッド内で認証してるので利用。
  # 直接認証メソッドを叩くなら、srv.instance_eval('auth')
  srv.calendars
rescue GoogleCalendar::AuthenticationFailed => e
  # 認証失敗
end

Flickr

http://flickr.com/services/api/auth.howto.web.html

RubyFlickr認証

認証のための、使い易いライブラリが見つからなかったので簡単につくってみた。
flickr/api/auth.rb

require 'digest/md5'
require 'uri'
require 'open-uri'
require 'rexml/document'

module Flickr

  module API

    class AuthError < RuntimeError;end

    class Auth

      def initialize(params)
        @api_key = params[:api_key].to_s
        @secret = params[:secret].to_s
      end

      def login(frob)
        return nil if frob.to_s.empty?
        uri = service_uri('rest')
        params = {
          :api_key => @api_key,
          :method => 'flickr.auth.getToken',
          :frob => frob.to_s
        }
        uri.query = params.merge!(:api_sig => sigunature(params)).collect { |key, value| "#{key}=#{value}"}.join('&')
        doc = REXML::Document.new(uri.read)
        rsp = doc.elements['//rsp']
        if rsp.attributes['stat'] == 'fail'
          raise AuthError.new(rsp.elements['err'].attributes['msg'])
        end
        auth = doc.elements['//auth']
        {
          :token => auth.elements['token'].text,
          :perms => auth.elements['perms'].text,
          :user => {
            :nsid => auth.elements['user'].attributes['nsid'],
            :username => auth.elements['user'].attributes['username'],
            :fullname => auth.elements['user'].attributes['fullname'],
          }
        }
      end

      def uri_to_login(perm = 'read')
        uri = service_uri('auth')
        params = {
          :api_key => @api_key,
          :perms => perm
        }
        uri.query = params.merge!(:api_sig => sigunature(params)).collect { |key, value| "#{key}=#{value}"}.join('&')
        uri
      end

      private
      def service_uri(path = 'auth')
        URI.join('http://flickr.com/services/', path)
      end

      def sigunature(params)
        Digest::MD5.hexdigest(@secret+params.sort_by{|key, value| key.to_s }.flatten.join).to_s
      end

    end

  end

end

こんなかんじに使う。

require 'flickr/api/auth'
require 'cgi'

params = {
  :api_key=>"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  :secret=>"xxxxxxxxxxxxxxxx"
}

cgi = CGI.new

auth = Flickr::API::Auth.new(params)
if info = auth.login(cgi['frob'])
  # 認証成功
  # infoの例
  # info => {
  #   :user => {
  #     :nsid=>"000000@N00",
  #     :username=>"taslam",
  #     :fullname=>""
  #   },
  #   :token=>"72157603867040929-8ffc565113df0adc",
  #   :perms=>"read"
  # }
else
  # 認証失敗
  # auth.uri_to_login.to_s にリダイレクト要求
end

Jugem

http://jugemkey.jp/api/auth/

RubyJugem認証

llameradaさんがライブラリを公開してくれてるのでそれを使わせていただく。

require 'jugem/auth'
require 'cgi'

params = {
  :api_key => "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  :secret => "xxxxxxxxxxxxxxxx",
  :callback_url => "登録したURLを含むコールバック先。この例ではこのCGIのURL。",
  :perms => "read"
}

cgi = CGI.new
auth = Jugem::Auth.new(params)

begin
  jugem = auth.login(cgi['frob'].to_s)
  # 認証
rescue  Jugem::AuthError => e
  # 認証失敗
  # auth.uri_to_login.to_s にリダイレクト
end

OpenID

http://openid.net/

RubyOpenID

ruby-openidライブラリを使う。OpenID2.0にも対応しているので、Yahoo!のでも大丈夫。

gem install ruby-openid

immediateを使わない場合のサンプル。普通はこれでいいと思います。
Yahoo!OpenIDつくってるひとは、identifierは「yahoo.co.jp」だけで良いです。

openid.cgi

#!/usr/bin/ruby
require 'rubygems'
require 'openid'
require 'openid/store/filesystem'
require 'cgi'
require 'cgi/session'
require 'cgi/session/pstore'

@cgi = CGI.new
session = CGI::Session.new(@cgi,'database_manager' => CGI::Session::PStore)

def render(text)
  @cgi.out do
    text
  end
end

def redirect_to(url)
  print @cgi.header({'status' => '302 Found', 'Location' => url })
end

store = OpenID::Store::Filesystem.new('鍵交換データの保存先')
consumer = OpenID::Consumer.new(session, store)
if session[:verified_identifier]
  # ログイン済み
  render "#{session[:verified_identifier]}でログイン済み"
else
  if !@cgi['openid.mode'].empty?
    #認証結果をコールバックでもらった
    params = @cgi.params.clone
    params.each { |k, v| params[k] = v[0] }
    oid_res = consumer.complete(params, 'コールバックURL(自身のURL)')
    case oid_res.status
    when OpenID::Consumer::FAILURE
      # 認証に失敗した。メッセージがoid_res.messageで取得可能。
      render oid_res.message
    when OpenID::Consumer::SUCCESS
      session[:verified_identifier] = oid_res.display_identifier
      redirect_to 'ログイン後のページのURL'
    when OpenID::Consumer::CANCEL
      # 認証をユーザがキャンセルした
      render "キャンセルされました"
    end
  elsif !@cgi['openid_identifier'].empty?
    # フォームにIdentifier(URL)が入力されたので、認証処理を開始する

    # @cgi['openid_identifier']では以下の警告が出て動かない。
    # CAUTION! cgi['key'] == cgi.params['key'][0]; if want Array, use cgi.params['key']
    identifier = @cgi.params['openid_identifier'][0]
    oid_req = consumer.begin(identifier)
    # コンシューマ識別用URLはプロバイダの認証画面で表示されるURLっぽい。
    # 注意としては、コールバックURLはコンシューマ識別用URLが部分一致しなければならないこと。
    redirect_to oid_req.redirect_url('コンシューマ識別用URL', 'コールバックURL')
  else
    # フォーム表示
    render <<-FORM
<h1>OpenID サンプル</h1>
<form method="post">
  <input id="openid_identifier" name="openid_identifier" type="text" value="" />
  <input name="commit" type="submit" value="認証" />
</form>
FORM
  end
end

mixi

少なくとも一般公開されてる認証APIはいまのところなさげ。やるならMixiStationの足跡APIのハックとなりそう。
まちゅダイアリーで紹介されているように、ユーザーから受け取ったアカウントと、パスワード(のwsse:PasswordDigest)を使ってAPIを叩いて認証に成功するかどうか確認する。

Rubymixi認証
//wsse.jsを使わせていただく。
//http://rvr.typepad.com/wind/2005/07/wsse_for_javasc.html
function sendWSSE(form, requestURL) {
  var username = null;
  var password = null;
  var elements = form.getElements();
  elements.each(function(element, index) {
    if(element.getAttribute('name') == 'email') username = element.value;
    if(element.getAttribute('name') == 'password') password = element.value;
  });
    
  var wsseForm = document.getElementById('wsseForm') || document.createElement('form');
  wsseForm.setAttribute('id', 'wsseForm');
  wsseForm.action = requestURL;
  wsseForm.method = 'POST';
  var wsseField = document.getElementById('wsseField') || document.createElement('input');
  wsseField.setAttribute('id', 'wsseField');
  wsseField.name = 'wsse';
  wsseField.type = 'hidden';
  wsseField.value = wsseHeader(username, password);
  document.body.appendChild(wsseForm);
  wsseForm.appendChild(wsseField);

  wsseForm.submit();

  return false;
}
<form onsubmit="return sendWSSE(this, 'mixi.cgi');">
  mixi<br />
  e-mail:<input type="text" name="email" value="" /><br />
  password:<input type="password" name="password" value="" /><br />
  <input type="submit" value="ログイン" />
</form>
require 'cgi'
require 'open-uri'

def mixi_login(wsse)
  open('http://mixi.jp/atom/tracks', 'X-WSSE' => wsse)
  true
rescue  OpenURI::HTTPError => e
  false
end

@cgi = CGI.new
mixi_login(@cgi['wsse']) # => 認証成功したらtrue