OAuth Echo認証のRails Gemを作ってみた

Twitterの各ツイートに「いいね!」などのデータをつけるようなサービスを考えていて、ちょっと試作していたところで、そのAPIの認証にはOAuth Echoという方法がよさそうだということがわかりました。
そこで、その試作から認証部分を切り出して、OAuth Echo認証のサーバ側をRailsで使うためのGemを作ってみました。
ちょうど、@yusukeyさんが「第2.1回 Twitter API 勉強会 @東京」を開催されるということで、LTで話してきました。

OAuth Echo の概略

OAuthについてはほとんど説明していないので、詳しくはググってください(すみません)。OAuth 1.0はそこそこ複雑なのですが、OAuth Echoのしくみは簡単です。
Twitterには、使用するのに認証が必要な verify_credentials というAPIがあります。クライアントからこのAPIをリクエストするとき、このようにOAuthのAuthorizationヘッダをつけます(ヘッダを生成するためのsimple_oauthなどのライブラリを使うと楽です)。

GET /1/account/verify_credentials.json HTTP/1.1
Host: api.twitter.com
Authorization: OAuth oauth_consumer_key="GDdmIQH6jhtmLUypg82g",
oauth_nonce="oElnnMTQIZvqvlfXM56aBLAf5noGD0AQR3Fmi7Q6Y",
oauth_signature="U1obTfE7Rs9J1kafTGwufLJdspo%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1272325550",
oauth_token="819797-Jxq8aYUDRmykzVKrgoLhXSq67TEa5ruc4GJC2rWimw",
oauth_version="1.0"

クライアントがTwitPicに画像をアップロードするためには、リクエストをこのように変えるだけです。

POST /2/upload.json HTTP/1.1
Host: api.twitpic.com
X-Auth-Service-Provider: https://api.twitter.com/1/account/verify_credentials.json
X-Verify-Credentials-Authorization: OAuth realm="http://api.twitter.com/",
oauth_consumer_key="GDdmIQH6jhtmLUypg82g",
oauth_nonce="oElnnMTQIZvqvlfXM56aBLAf5noGD0AQR3Fmi7Q6Y",
oauth_signature="U1obTfE7Rs9J1kafTGwufLJdspo%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1272325550",
oauth_token="819797-Jxq8aYUDRmykzVKrgoLhXSq67TEa5ruc4GJC2rWimw",
oauth_version="1.0"

ヘッダ部分は、AuthorizationをX-Verify-Credentials-Authorizationに変えるだけで、ほぼ同じです。
TwitPicはこれを受け取って、Authorizationヘッダに戻し、そのままTwitterにリクエストします。Twitterからは認証の結果が返ってくるので、OK(200)ならばアップロードを許可するというしくみです。

エッセンス

Railsには、HTTP Basic認証を簡単にかけられる機能があります。

class PostsController < ApplicationController
  http_basic_authenticate_with :name => "tkawa", :password => "secret"

  def index
    render :json => { :message => "Limited Access" }
  end
  ...
end

http_basic_authenticate_withというメソッドを呼ぶだけです。これは便利だね、ってことで、この部分のコード(ActionController::HttpAuthentication::Basic)をかなり参考にして、似たインターフェイスでOAuth Echo認証をできるようにしました。

class PostsController < ApplicationController
  oauth_echo_authenticate_with :twitter

  def index
    render :json => { :message => "Limited Access", :user => @current_user }
  end
  ...
end

oauth_echo_authenticate_withというメソッドを呼ぶだけです。引数にはService Providerを指定しますが、現状:twitterにしか対応していません。
認証結果を自分でコントロールするために、before_filter内で使用できるメソッドも用意しています。

ソース

コード自体は60行ぐらいしかないごく短いもので、Gemを作る練習をしてみた、という感じです。見よう見まねで、Gemはこれでいいのかまだよくわかってません…。

Ustream

時間がある方は、当日のLTのUstream録画がありますので、よければそちらもごらんください。(よけいわかりにくいかもしれないので、ご意見ご指摘希望…)

@yusukeyさん、みなさんありがとうございました。

余談


みんな使ってるからということで、深く考えずSlideShareを使ったんだけど、どうやらSpeaker Deckがいいらしいという話も。どうなんでしょ。
一応Speaker Deckにも上げてみた。
OAuth Echo の Rails Gem - Speaker Deck

RailsでのURL設計を考えてみる(5) Railsのリソースパターン

URL設計の前段階として、とても大切なのがリソース設計です。そのWebアプリ・Webサービスで何を提供するのかが決まる部分だからです。しかし、なかなかリソースという概念が定着していないようなので、Railsで採用されているパターン*1を例に挙げて紹介してみたいと思います。
今までのシリーズ記事と重なるところもありますが、まとめということで…。

リソースとは

簡単に言うと、「URLで示されるもの」です。URLというのが“Uniform Resource Locator”の略ですからね。

http://d.hatena.ne.jp/tkawa/20110819/p1
http://d.hatena.ne.jp/tkawa/20110819

最初のものは、前回書いたブログ記事『RailsでのURL設計を考えてみる(4) スラッシュと「持っている」関係』というリソースです。
その次は、『tkawaが2011年8月19日に書いたブログ記事』というリソースです。実際には中身の記事は1つしかないのですが、もし複数の記事があれば列挙されることになります。
これに限らず、URLで示すことができる情報はすべてリソースです。URLはリソースにつけられた名前ともいえます*2

Railsで用意されているパターン

すべてがリソースなのでいろいろな種類がありうるのですが、その中でも典型的なパターンがいくつか存在します。Railsでは、そのパターンに沿って設計すると簡単に書けるようになっています。

MemberリソースとCollectionリソース

Memberリソースは一番基本となるリソースです。1個のオブジェクトや、データベースの1レコードを指すというイメージです。
このようなURLがMemberリソースの例です。

http://d.hatena.ne.jp/tkawa/20110819/p1
http://baseball.example.jp/leagues/central
http://railsapp.example.com/users/12

Collectionリソースは、Memberリソースを種類ごとにまとめたもので、ちょうどディレクトリ(フォルダ)のようなイメージです。
例はこのようなURLです。

http://d.hatena.ne.jp/tkawa/20110819
http://baseball.example.jp/leagues
http://railsapp.example.com/users

見ると気づきますが、Memberリソースの末尾を削ったものになっています。言い換えると、Collectionリソースの後ろにスラッシュ区切りでIDをくっつけることで、MemberリソースのURLになります。

Member, CollectionリソースのURLの構造*3

/{collection_name}/{id}
/{collection_name}

Collectionリソースの名前(collection_name)は名詞の複数形が推奨されます。Memberリソースの名前(id)は、そのリソースをCollection内で一意に特定できる名前、もしくは番号です。

Singular(単数)リソース

Singularリソースは、Memberリソースと似ていますが、ただ1つしかないリソースです。これはグローバルに1つしかないという場合のほかにも、あるMemberリソースに1対1で結びついている場合、セッションなどの暗黙のパラメータに対して1つしかない場合も含まれます。
例はこのようなURLです。

http://b.hatena.ne.jp/tkawa/config
http://railsapp.example.com/users/12/profile
http://u2plus.jp/user

最後の例は、僕が制作に関わっているサービスのU2plusの例なのですが、“/user”で「現在ログインしているユーザ」というリソースを指すことにしています。これはセッションに対して1つしかないリソースになっています。
Singularリソースの名前は、名詞の単数形が推奨されます。

routes.rbの書き方

上記のパターンに従えば、routes.rbの書き方はとても簡単です。

Memberリソース、Collectionリソースの場合
resources :users

これで、“/users”というCollectionリソースと、“/users/{id}”というMemberリソースができます。

Singularリソースの場合
resource :user

これで、“/user”というSingularリソースができます。

組み合わせ

組み合わせるときも、このようにネストするだけです。

resources :users do
  resource :profile
end

これで、“/users”、“/users/{id}”に加えて、“/users/{id}/profile”というSingularリソースができます。

リソース設計の指針

Railsで採用されているリソースのパターンはかなり一般的で、多くの部分に適用できるので、リソース設計の指針になります。さらにRailsではそれに対応して自動的にURLやコントローラの設計も決まります。
もちろんもっと細かく設計したいこともあるでしょう。そのときはこれをベースにカスタマイズしていけばいいわけです。そのための方法も十分に用意されています。

また、Railsだけでなく、Sinatraなどのほかのフレームワークを使って設計するときも、このパターンをまず参考にしてみるのがいいと思います。

*1:各書籍を見ると「リソースモデル」という用語が主に使われているようですが、Railsでは「モデル」が紛らわしいので、ここではあえて「パターン」と呼ぶことにします

*2:ではURL=リソースなのかというと、同じリソースが複数のURLをもつこともあるので、厳密には違うのですが、ほぼ同じと思っても問題ないでしょう

*3:{ } は、URIテンプレートというURL中にパラメータを含むときの記法です

OmniAuthで認証やコールバックのURLを変える方法

ほとんど需要がないと思われるけどメモ。
Rails + OmniAuthで例えばTwitter認証をするとき、デフォルトで認証時のURLは /auth/twitter で、コールバックURLは /auth/twitter/callback になる。
このURLを変更するには、config/initializers/omniauth.rbにこんなふうに書く。

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, 'TWITTER_CONSUMER_KEY', 'TWITTER_CONSUMER_SECRET',
           :request_path => '/sign_in', :callback_path => '/sign_in/callback'
end

ほかに、:path_prefix とか :setup_path とか使えるようです。:path_prefix はデフォルトが '/auth' なので、ここだけ変えたいときにおすすめ。(といってもURL変えたい人なんてほとんどいないと思うけど)
Railsでなくても同様にproviderの記述を変えればできるはず。

参考ソース
    def path_prefix
      options[:path_prefix] || OmniAuth.config.path_prefix
    end

    def request_path
      options[:request_path] || "#{path_prefix}/#{name}"
    end

    def callback_path
      options[:callback_path] || "#{path_prefix}/#{name}/callback"
    end

    def setup_path
      options[:setup_path] || "#{path_prefix}/#{name}/setup"
    end
https://github.com/intridea/omniauth/blob/v0.3.0/oa-core/lib/omniauth/strategy.rb

ちなみに

Rails3レシピブックやいろんなWebの解説を見ても、routes.rbに

  match "/auth/:provider/callback" => "sessions#callback"

これしか書いていないのに /auth/twitter というURLがちゃんと機能することについて全く説明がなかったので混乱してしまいました。
これは実はRackミドルウェアいうRailsとは別の仕組みなので、routes.rbは関係なかったということです。全く知らなかった…。そういえばレシピブックにも「OmniAuth は Rack ミドルウェアとして利用できます」って書いてあるし。でもわかりにくい。
Rackレベルだから別というのはわかったけど、ちょっとキモイですね。

LokkaのテーマをGitHubからインストールできるプラグインを書いてみた

LokkaというRubyで書かれたCMSがあります。とてもシンプルで、クラウドに対応しているのでとくにHerokuではとても簡単にセットアップしてブログなどを作れます。
ただ、Herokuでは基本的にサーバ上にファイルを書き込めないので、WordPressのように管理画面から新しいテーマ(テンプレート)をインストールすることができません*1
ソースコードを見てみたところ、Lokkaで利用しているフレームワークSinatraでは、HTMLのテンプレートは静的ファイルでなくても文字列として読み込んでおけばいいことがわかったので、erbなどのテンプレートファイルだけ読み込んで、画像などは外部ホストを使えばいいんじゃないかと考えて、Lokkathonの時間などで試しにプラグインとして作ってみました。

しくみ

だいたいのしくみはこんな感じです。

  • GitHub上に置いたテーマを、git cloneAPP_ROOT/tmpに取得
  • erbなどのテンプレートファイルだけ文字列として読み込む
  • テーマ内の画像ファイルなどのルートパスにGitHub PagesのURL(http://{username}.github.com/{reponame}/)を設定

GitHub Pagesをホスティングに使ってしまうというかなり強引な方法で、ひょっとするとまずいかもしれませんが動作しました。管理画面はこんな感じ。

右のテーマは @monoooki さんが作られた @jishiha さんのブログ「僕は発展途上技術者」用のテーマです。動作確認に使わせていただきました。
この画面はlocalhostでの動作ですが、Heroku上でも動作します。原理的にはgit cloneの実行とAPP_ROOT/tmpへの書き込みが可能な環境なら動くと思います。
まだ実装していませんが、ルートパスを別途設定できるようにすれば、GitHub Pages以外のホスティングを利用するようにもできます。

きっかけ

Lokka作者の @komagata さんや、プラグイン機構を作った @yagi_ さんの、

プラグインの管理画面をいじっていて思ったのですが、このプラグイン機構の
単純さこそlokkaの面白さだったのが、管理画面化したってどうせウェブサーバを
再起動させることが必須になるのだからWordPress並みのものにはひっくり返っても
ならないのでふてくされていました。

https://groups.google.com/group/lokka-ja/msg/8458436d71d92712?hl=ja

などの話を見て、なんとか再起動なしにWordPressみたいなプラグインON/OFFができないかと思って、最初プラグイン機構のほうをいじっていました。
一応単一ファイルならevalして即反映できるところまでは行っています。作りかけはこれ GitHub - tkawa/lokka-instant_plugin: Lokka Plugin for instant add/remove (trial)
そのあと、デザイナーの @machida さんから、「WordPressみたいに1クリックでテーマをインストールできたらいいのに」という話を聞いたので*2、そっちのほうが需要があるのかもと思って、作りかけのプラグイン機構を流用して作ってみた、という感じです。

インターフェイス的には本体のテーマ選択画面と一体にしたほうがいいのですが、まだ作ってみた段階で完成度が低いということで、プラグインの形式にしてあります。本体にマージしたほうがいいかも?
でもまだテーマ自体が少ないので、現段階ではあまり使わないかもしれないですね。

*1:WordPress使ったことないのでほんとはよく知らないのですが

*2:WebデザイナーのためのGit勉強会 : ATNDの懇親会

リンクとボタンは区別できるようにしてほしい

こんな感じ*1。まあ、文句言うならパッチ書けよって感じではありますが…。

http://www.antimon2.atnifty.com/works/slide_20110828/images/localhost3001_books_snapshot.png

http://www.antimon2.atnifty.com/works/slide_20110828/#slide19

clickイベントに使うためだけに<a href="#">text</a>とか(下の例の1,2番目)やるのが嫌いで、どうやるのがスマートorシンプルなんだろと思って書き出したもの。
(中略)
1番目は今時ない気がするし、1,2番目は状態が遷移しないのにhref指定してると、ミドルクリックなどで別のページとして開けたりしちゃうのでそれを抑制するコードも必要になるのが何か嫌で、3番目当たりが個人的には好きだった。(4番目は何か気持ちわるい)

<div><a href="javascript:void 0;">javscript:ボタン</a></div>
<div><a href="#">#なボタン</a></div>
<div><a role="button">hrefがないroleボタン</a></div>
<div><span role="button">リンクじゃないspanボタン</span></div>
クリッカブルなボタンとWAI-ARIAのrole=”button”について | Web Scratch

これに関しては、本文でもブクマコメントでも言及されてるけど、普通にbutton要素を使うのがいちばん無難そう。

*1:適当に検索して見つけたスクリーンショットなのでただの例です。「HTML5 on Rails3.1」というわかりやすいスライドです。

pjaxではURLを分けるべき

pjaxが便利過ぎて鼻血出そうになった(railsのサンプル付き) - SELECT * FROM life;』を読んで、具体的にはどうなってるのかなと思ってGitHubのjquery-pjaxのソースを読んでて見つけたことのメモ。

pjax.defaults = {
  timeout: 650,
  push: true,
  replace: false,
  // We want the browser to maintain two separate internal caches: one for
  // pjax'd partial page loads and one for normal page loads. Without
  // adding this secret parameter, some browsers will often confuse the two.
  data: { _pjax: true },
  type: 'GET',
  dataType: 'html',
  beforeSend: function(xhr){
    this.trigger('start.pjax', [xhr, options])
    xhr.setRequestHeader('X-PJAX', 'true')
  },
https://github.com/defunkt/jquery-pjax/blob/master/jquery.pjax.js

RailsでのURL設計を考えてみる(4) スラッシュと「持っている」関係

今回は、URLのスラッシュはどのようなときに使えばいいのか考えてみました。

resourcesのネスト

URLはスラッシュ「/」で階層構造を表します。Railsでは、routes.rbでresourcesをこのようにネストして使うと、

resources :users do
  resources :articles
end

このようなURLができます。

http://example.com/users
http://example.com/users/1
http://example.com/users/1/articles

/users で「ユーザー全体」、 /users/1 で「ユーザーの1番」を指し、さらに /users/1/articles で「ユーザーの1番」の書いた「記事(article)全体」を指すわけです。((さらに /users/1/articles/2 で「ユーザーの1番」の書いた「記事の2番」を指すURLもできますが、Railsではidは基本的にグローバルであることから、 /articles/2 で十分なのであまり必要とは思いません。resources :articles, :only => :indexなどとすることで除くことができます。))
このように、「持っている」has_one(1:1)やhas_many(1:n)の関係があるときに、階層とみなしてスラッシュでつなげたURLをつくるのが一般的になっています。

例えば、userとarticleがそれぞれhas_many commentsの関係のとき、

/users/1/comments
/articles/2/comments

というURLをつくることができます。意味が分かりやすいですね。

belongs_toのときは?

ところで、僕が今作っているRailsアプリでは、「ユーザーの1番」が「コメント」した「記事全体」のURLが必要になりました。comment belongs_to articles の関係です。
さて、これはどう表現すればいいでしょう?

(1)
/users/1/comments/articles

すぐ上からの類推です。うーん、belongs_toのときはなんか違和感があるのは僕だけでしょうか。コメントが記事を持っているわけではないのです。

(2)
/articles?commented_by_user_id=1

記事全体から、条件のパラメータで抽出したという感じ。素直で意味はわかりやすいのですが、もうちょっときれいな感じにできないでしょうか。

(3)
/users/1/commented/articles

(1)とほとんど変わらないじゃん!と思われるかもしれませんが、大きく違う点は「commented」です。これは言うなれば「commented_resources」の略で、記事を「持っている」関係になっていることがわかります。
コメントがつけられるのが記事だけならば、スラッシュは使わずに /users/1/commented_articles としてもいいのですが、今作っているアプリでは記事以外のものにもコメントがつけられるようになっていたということ、そしてリソース名を「articles」に統一することで、同じインターフェイスであることを示唆する効果があります。

routes.rbの書き方

(3)ではこう書けます。

resources :users do
  resource :commented, :only => :show do
    resources :articles, :only => :index
  end
#  CommentedsControllerが不要な場合は以下でもよい
#  resources :articles, :path => 'commented/articles', :as => 'commented_articles', :only => :index
end

ルーティングはこうなります。(users部分は省略)

user_commented_articles GET /users/:user_id/commented/articles(.:format) {:action=>"index", :controller=>"articles"}
         user_commented GET /users/:user_id/commented(.:format)          {:action=>"show", :controller=>"commenteds"}

本来「commented」は単数リソースではないのですが、resourcesのネストでは必ずidが入ってしまう(/users/:user_id/commented/:commented_id/articles となる)collection do ... end に resources をネストさせるとなぜかおかしくなるので、単数扱いにしてresourceを使用します。

/users/:user_id/commented というURLは使わない場合も多いので、:pathと:asを使った書き方もOKです。今は使わないとしてもcommentedがリソースとして「意味を持つ」ということが重要です。ちなみに、scopeを使って

  scope :path => 'commented', :as => 'commented' do
    resources :articles, :only => :index
  end

でもできるはずですが、なぜか手元ではうまくいきませんでした。

スラッシュの条件

スラッシュを使って「A/B/C」というURLをつくるとき、こういうことが言えると思います。

  • AはBを、BはCを持っている関係
  • 「A/B」も「A」もリソースとして意味を持つ

これは、階層構造と考えると自然なことです。

もちろん、階層構造がなじまないデータもあるので、それを表現するときは別の手段を考えることになります。それはまたネタができれば書いてみたいかも?