ゼロから学ぶOAuth

第3回OAuth Consumerの実装(応用 : smart.fm APIおよびGoogle Data APIsの利用)

今回は、OAuth Consumerの実装の応用として、smart.fm APIとGoogle Data APIsの利用について解説します。

ruby-oauth の使い方

今後RubyでOAuth ConsumerおよびOAuth Service Providerを実装する場合は、ruby-oauthを利用することになるでしょう。第2回でruby-oauthのインストールは終わっているはずですが、まだインストールしていない人は以下を実行してください。

gem install oauth

Ruby OAuth GEMのサイトにも利用方法が紹介されていますが、実際には各Service Providerが要求するパラメータがあったりするため、この通りには使えません。またサイトではversion 0.2.7となっていますが、実際の最新は0.3.1ですので最新版を利用した方が良いでしょう。

そこで今回はこのruby-oauthの実際の使い方を学びながら、smart.fm APIおよびGoogle Data APIsのうちGoogle Contacts Data APIを利用してみましょう。OAuthでConsumerとService Providerが直接通信するシチュエーションは以下の3つです。

  • 未認可のRequest Tokenを取得する
  • 認可済のRequest TokenをAccess Tokenと交換する
  • Access Tokenを利用した認可を必要とするデータへアクセスする

またそれ以外にブラウザのリダイレクトを利用する通信を行うシチュエーションが2つあります。

  • 未認可のRequest Tokenに対してユーザの認可を求める
  • 認可済のRequest TokenをConsumerに渡す

基本的には、この5種類の通信を扱えればOAuthを利用できます。

注)
Yahoo! OAuthなど、一部のService ProviderではAccess Tokenの有効期限の延長が必要な例などもありますが、ここではそういった例は扱いません。

そして、この5種類の通信を扱う為に必要なruby-oauthのクラスは、OAuth::Consumer/OAuth::RequestToken、OAuth::AccessTokenの3つのみです。よってこの3つの利用方法を覚えれば、OAuth Consumerの実装が可能になります。

OAuth::Consumerの利用

未認可のRequest Tokenを取得するには、OAuth::Consumer#get_request_tokenを利用します。またOAuth::RequestToken、OAuth::AccessTokenのインスタンスを作成する際にも、OAuth::Consumerインスタンスが必要です。そこでまずはOAuth::Consumerのインスタンスを作成しましょう。

@consumer = OAuth::Consumer.new(
  CONSUMER_KEY,
  CONSUMER_SECRET,
  options
)

ここでoptionsには以下のようなパラメータが指定可能です。

options = {
  :site               => SERVICE_PROVIDER_URL,
  :request_token_url  => SERVICE_PROVIDER_REQUEST_TOKEN_URL,
  :request_token_path => SERVICE_PROVIDER_REQUEST_TOKEN_PATH,
  :authorize_url      => SERVICE_PROVIDER_AUTHORIZE_URL,
  :authorize_path     => SERVICE_PROVIDER_AUTHORIZE_PATH,
  :access_token_url   => SERVICE_PROVIDER_ACCESS_TOKEN_URL,
  :access_token_path  => SERVICE_PROVIDER_ACCESS_TOKEN_PATH,
  :scheme             => (:header || :body || :query_string),
  :http_method        => (:post || :get), 
  :signature_method   => ('HMAC-SHA1' || 'RSA-SHA1' || 'PLAINTEXT'),
  :private_key_file   => PATH_TO_PRIVATE_KEY,
  :oauth_version      => "1.0"
}

siteは必須、oauth_versionは指定不可(1.0で固定)です。それ以外のパラメータは以下のように利用されます。

request_token_(url|path)、authorize_(url|path)、
access_token_(url|path)

xxx_urlはxxx_pathよりも優先されます。xxx_pathのみを指定した場合はsiteの値が用いられます。

xxx_url = site + xxx_path

何も指定しない場合はそれぞれ以下のデフォルト値が利用されます。

:request_token_path => '/oauth/request_token'
:authorize_path     => '/oauth/authorize'
:access_token_path  => '/oauth/access_token'

signature_method、private_key_file

Service Providerごとにサポートされているsignature_methodは異なりますが、通常はHMAC-SHA1を利用すれば良いでしょう。ruby-oauthでもHMAC-SHA1がデフォルト値になっていますので、特に指定することはありません。signature_methodにRSA-SHA1を利用する場合は、private_key_fileに秘密鍵のパスを指定します。またhttps通信が利用できない場合はPLAINTEXTは避けましょう。

scheme、http_method

OAuthでは通常はAuthorizationヘッダを利用して通信を行いますが、それ以外にもPOSTアクセスではRequest Body、GETアクセスではquery_stringを利用することもできます。ruby-oauthのデフォルトは以下のようになっています。

:scheme      => :header
:http_method => :post

こちらもService Provider側から特に指定の無い限り、ruby-oauthのデフォルト値で問題ありません。

OAuth::RequestTokenの利用

OAuth::RequestTokenはRequest Tokenを保存するクラスです。OAuth::RequestTokenインスタンスを取得するには、OAuth::Consumer#get_request_tokenを利用します。Google Data APIsのscopeパラメータ、smart.fm APIのAPI Keyなどはget_request_tokenの第2引数(以下のrequired_parameters)で指定します。

@request_token = @consumer.get_request_token({}, required_parameters)

この時点で@request_tokenは未認可ですので、ユーザをauthorize_urlにリダイレクトさせます。またtokenとsecretは後で利用しますので、sessionに保存しておきます。

このauthorize_urlにはCallback URLを含めることもできます。Callback URLを利用する場合は、authorize_urlにoauth_callbackというURLパラメータを付加することになります。通常Service ProviderではConsumer登録時にデフォルトCallback URLを登録させますが、oauth_callbackが指定されていればそちらを優先します。

session[:request_token] = @request_token.token
session[:request_token_secret] = @request_token.secret
redirect_to @request_token.authorize_url + "?oauth_callback=#{callback_url}"

ユーザがRequest Tokenに認可を与えた後Request TokenとAccess Tokenを交換する為にも、OAuth::RequestTokenを用います。

request_token = OAuth::RequestToken.new(
  consumer,
  session[:request_token],
  session[:request_token_secret]
)

@access_token = request_token.get_access_token

OAuth::AccessTokenの利用

Access Tokenが必要なアクセスは、OAuth::AccessTokenインスタンスを経由して行うことになります。GET / POST / PUT / DELETEは、それぞれ以下のように行えます。ちなみに第2回(http://gihyo.jp/dev/feature/01/oauth/0002)最後のサンプルコードではPOSTを用いました。

access_token = OAuth::AccessToken.new(
  consumer,
  stored_token,
  stored_token_secret
)

# GET
access_token.get(PATH_OR_URL)
access_token.get(PATH_OR_URL, HEADER)

# POST
access_token.post(PATH_OR_URL, BODY)
access_token.post(PATH_OR_URL, BODY, HEADER)

# PUT
access_token.put(PATH_OR_URL, BODY)
access_token.put(PATH_OR_URL, BODY, HEADER)

# DELETE
access_token.delete(PATH_OR_URL)
access_token.delete(PATH_OR_URL, HEADER)

smart.fm APIとGoogle Contacts Data APIを利用する

さて、ここまでで紹介した使い方で、OAuth Consumerとして必要なことはおおよそつかんでいただいたはずです。Consumer実装の最後では、smart.fm APIとGoogle Data APIsを利用する際に注意すべきことを述べておきます。

smart.fm APIの利用

smart.fm OAuthを利用する際に注意すべき点として、Authorize URLがRequest Token URLとAccess Token URLとは別ドメインになっています。またsmart.fmのOAuthを利用するには、Consumer Keyの他にAPI Keyが必要です。そのため前述のget_request_tokenメソッドの引数のrequired_parametersの部分にapi_keyを指定する必要があります。

required_parameters = {api_key => SMARTFM_API_KEY}
@consumer.get_request_token({}, required_parameters)

またAPI KeyはAccess Token取得時にも必要ですが、ruby-oauth ver.0.3.1ではget_access_tokenでAPI Keyを渡すができないため、第2回(http://gihyo.jp/dev/feature/01/oauth/0002)で紹介したサンプルアプリケーションでは以下のようにパッチを当てています。get_access_tokenでのService Provider独自パラメータの利用は、ruby-oauthの次期バージョンでサポートされる予定ですが、それまではこのようなパッチを当てて対応するしか無いでしょう。

# Hacks
# Support params in get_access_token request for smart.fm API's api_key
class OAuth::RequestToken
  def get_access_token_with_args(options = {}, *args)
    access_token_url = consumer.access_token_url? ? consumer.access_token_url : consumer.access_token_path
    response = consumer.token_request(consumer.http_method, access_token_url, self, options, *args)
    OAuth::AccessToken.new(consumer, response[:oauth_token], response[:oauth_token_secret])
  end
  alias_method :get_access_token_without_args, :get_access_token
  alias_method :get_access_token, :get_access_token_with_args
end

またAccess Tokenを取得した後、smart.fm APIを利用するには、smart.fm gemを利用するのが一番簡単だと思います。

gem install smartfm

このgemは現在smart.fm APIで提供されているほぼ全ての機能をサポートしています。また新しい機能がリリースされた場合にも、なるべく早く対応していきます。smart.fm gemでは独自のSmartfm::AuthクラスでOAuthサポートを行っていますが、もちろん内部的にはOAuth::ConsumerとOAuth::AccessTokenを使っています。

認可が必要なAPIは、以下のようにSmartfm::Authのインスタンスを引数に渡して利用します。⁠Basic Authも利用可能)

require 'rubygems'
require 'smartfm'
require 'oauth/consumer'

Smartfm::Config.init do |conf|
  conf.api_key               = YOUR_API_KEY
  conf.oauth_consumer_key    = YOUR_OAUTH_CONSUMER_KEY
  conf.oauth_consumer_secret = YOUR_OAUTH_CONSUMER_SECRET
end

auth = Smartfm::Auth.new(
  :token => OAUTH_ACCESS_TOKEN,
  :secret => OAUTH_ACCESS_TOKEN_SECRET
)

## BASIC AUTH
# auth = Smartfm::Auth.new(
#   :username => USERNAME,
#   :password => PASSWORD
# )

# List API
@list = Smartfm::List.create(
  auth,
  :title => 'smart.fm gem test',
  :description => 'A list for smart.fm gem test'
)
@list.add_item(auth, Smartfm::Item.find(437525))

# Item API
@item = Smartfm::Item.create(
  auth,
  :cue => {
    :text           => 'hello world!',
    :language       => 'en',
    :part_of_speech => 'E'
  },
  :response => {
    :text     => 'ハローワールド!',
    :language => 'ja'
  }
)
@item.add_image(auth, 'http://farm4.static.flickr.com/3276/3102381796_a33c1ffdf1.jpg')
@item.add_sound(auth, 'http://matake.jp/download/hello_world.mp3')
@item.add_tags(auth, 'sample', 'programming')

# Sentence API
@sentence = Smartfm::Sentence.create(
  auth,
  :text => 'Hello World!',
  :item => Smartfm::Item.matching('hello world').first
)
@sentence.add_image(auth, 'http://farm4.static.flickr.com/3276/3102381796_a33c1ffdf1.jpg')
@sentence.add_sound(auth, 'http://matake.jp/download/hello_world.mp3')

その他の認可の必要ないAPIの利用方法はsmart.fm gemのexamples/pure_ruby.rbをご覧ください。

Google Contacts Data APIの利用

Google Data APIsではscopeをサポートしています。例えばGoogle Contacts Data APIにアクセスする場合は"https://www.google.com/m8/feeds"、Google Calendar APIにアクセスする場合は"http://www.google.com/calendar/feeds"というscopeに対して認可を得たAccess Tokenが必要になります。

さらにGoogleの特徴として、scopeパラメータがRequest Token取得時に必須になっています。同じくscopeをサポートしているYahoo! OAuthでは、Consumer登録時にscopeを決定してしまうのに対して、Googleは新規に発行されるRequest Tokenごとにscopeを設定しているため、このような仕様になっているようです。

required_parameters = {:scope => "https://www.google.com/m8/feeds"}
@consumer.get_request_token({}, required_parameters)

ruby-oauthでGoogle OAuthを利用する場合は、scopeパラメータとrequest_token_path、authorize_path、access_token_pathの各エンドポイント以外はruby-oauthのデフォルト設定のままでOKです。ただしGoogleはsignature_methodとしてRSA-SHA1のサポートも行っているので、RSA-SHA1を使いたい場合はsignature_methodとprivate_key_fileを指定してください。

その他にはGoogle Apps専用に"2-legged OAuth"と呼ばれるユーザを挟まないOAuthを提供していたり、OpenIDとOAuthのハイブリッド仕様を提供していたりします。詳しくはOAuth Authentication for Web ApplicationsFederated Login for Google Account Usersをご覧ください。

補足:Yahoo! OAuthの利用

Yahoo! OAuthは若干特殊な仕様になっているため、ruby-oauthをそのまま利用することはできません。ここでは詳しい利用方法は紹介しませんが、興味のある方はruby-oauth で Yahoo! OAuth を使う方法を参考にしてください。

Yahoo! Japanでも今年夏までにはOAuthの提供が開始される予定ですので、今後OAuth Consumerを実装される方はYahoo! OAuthの動向にも注目しておくと良いでしょう。

OAuth Consumer開発者の皆様へ

OpenIDでは世界的にも成功事例として認識されている日本ですが、OAuthはまだあまり浸透していないようです。しかしOpenID + OAuthハイブリッド仕様やYahoo! JapanのOAuthリリースなど、今後ますますOAuthの利用価値が高まることと思います。

今後ますます国内のOAuth Consumerが増えていくことを期待しています。

おすすめ記事

記事・ニュース一覧