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リソースができます。
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" endhttps://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 clone
でAPP_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_ さんの、
プラグインの管理画面をいじっていて思ったのですが、このプラグイン機構の
https://groups.google.com/group/lokka-ja/msg/8458436d71d92712?hl=ja
単純さこそlokkaの面白さだったのが、管理画面化したってどうせウェブサーバを
再起動させることが必須になるのだからWordPress並みのものにはひっくり返っても
ならないのでふてくされていました。
などの話を見て、なんとか再起動なしにWordPressみたいなプラグインON/OFFができないかと思って、最初プラグイン機構のほうをいじっていました。
一応単一ファイルならevalして即反映できるところまでは行っています。作りかけはこれ GitHub - tkawa/lokka-instant_plugin: Lokka Plugin for instant add/remove (trial)
そのあと、デザイナーの @machida さんから、「WordPressみたいに1クリックでテーマをインストールできたらいいのに」という話を聞いたので*2、そっちのほうが需要があるのかもと思って、作りかけのプラグイン機構を流用して作ってみた、という感じです。
インターフェイス的には本体のテーマ選択画面と一体にしたほうがいいのですが、まだ作ってみた段階で完成度が低いということで、プラグインの形式にしてあります。本体にマージしたほうがいいかも?
でもまだテーマ自体が少ないので、現段階ではあまり使わないかもしれないですね。
リンクとボタンは区別できるようにしてほしい
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が入ってしまう(collection do ... end に resources をネストさせるとなぜかおかしくなるので、単数扱いにしてresourceを使用します。/users/:user_id/commented/:commented_id/articles
となる)
/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」もリソースとして意味を持つ
これは、階層構造と考えると自然なことです。
もちろん、階層構造がなじまないデータもあるので、それを表現するときは別の手段を考えることになります。それはまたネタができれば書いてみたいかも?