0

SinatraでGitHub OAuthする (2)

SinatraでGitHub OAuthするの続き

試しにGitHub認証して、成功したらリポジトリとGistの一覧を表示するだけのアプリを作ってHerokuに置いておいた
http://sinatra-github-oauth-sample.herokuapp.com/

ソースコード
shokai/sinatra-github-oauth-sample · GitHub


https://github.com/settings/applicationsからRegister new Applicationした
OAuthのコールバックは /auth.callback に来る。



右上からログインすると


一度GitHubに移動してから戻ってきて、GitHub APIが使われてリポジトリとGistの一覧が出てくる
右上にGitHubに登録したアイコンが表示される。


1. GitHub認証して(これはSinatraでGitHub OAuthするに書いた)
2. 乱数とMD5でセッションID作ってブラウザに渡して
3. memcachedにセッションIDをkeyにしてgithubの情報を入れておく(2週間でexpireする)
4. トップページでは、ログイン状態ならoauth tokenをoctokitに渡してGitHubからリポジトリ/Gist一覧を取得する(これはmemcacheで1時間保持する)

必要な時にmemcachedからユーザー情報を取り出せるhelperを書いた
libs/cache.rbcontroller/auth.rbに処理を詰め込んであるので、この2つだけコピーすれば他のSinatraアプリにもGitHubログイン機能追加できるはず。

RackやSinatraのログイン系のプラグインを使っても良いかと思ったが、memcachedクライアントにpure rubyかつバイナリプロトコルサポートしているdalliを使いたかったのと、
1つのmemcachedのkeyにprefix付けてログイン情報とリポジトリ/Gist一覧を保存できるようにしたかったので自分で書いた libs/cache.rb に任せた。配列のようにアクセスするとkeyにprefixが付いて読み書きできて便利。

0

SinatraでGitHub OAuthする

Webアプリに「GitHubで認証」ボタンを付けてログインさせる方法。


アプリを登録

https://github.com/settings/applications
からRegister new Applicationする。

ローカルで試すので、URLとコールバックURLはこうしておく

自分のアプリケーションのIDとsecret keyがもらえるのでメモしておく。(あとで使う)


OAuthする

ここに手順が書いてある。この通りにやればOAuthのtokenが手に入る。
OAuth | GitHub API
tokenが手に入れば、あとはoctokitなどにtokenを渡せばそのユーザーの権限でAPIを使わせてもらえる。


手順はおおまかに

1. GET https://github.com/login/oauth/authorize

ユーザーのWebブラウザをGETパラメータにアプリケーションID付けて上のURLにredirectする。
OAuthの承認画面が表示され、OKされればアプリ登録時に設定したURLにcallbackされる。

2. コールバックから”code”を取り出す。

callback URLに指定した通り、ユーザーのWebブラウザで http://localhost:5000/auth.callback が開かれる。
その時にGETパラメータで”code”が付いているので、実際のURLは
http://localhost:5000/auth.callback?code=1234abcd56
こうなる。
codeを取っておく。

3. POST https://github.com/login/oauth/access_token

Sinatraから上のURLにPOSTする。
“code”とアプリケーションIDとsecretをPOSTすると、ようやくOAuthのtokenが得られる。

4. tokenでOAuth認証

tokenで認証して、ようやく「この人がGitHub上でなんというアカウント名なのか」「持っているリポジトリ一覧」などが取得できるようになる。
ここからはoctokit使えばいいと思う。

なお1の時にscope(読み書き権限の詳細)やstate(クロスサイトリクエスト対策の文字列)を付ける事もできる。
無くても一応動く。


実装

起動前にRegister new Applicationした時に得たアプリケーションIDとsecretを環境変数に入れておく。
export GITHUB_APP_ID=a1b2cdef344565677asdf
export GITHUB_APP_SECRET=asdfhujikohujiko123456


あとはこのアプリを起動して、 /auth に移動させれば認証できる。

require "sinatra"
require "uri"
require "httparty"

get '/auth' do
query = {
:client_id => ENV["GITHUB_APP_ID"],
:redirect_uri => "#{env['rack.url_scheme']}://#{env['HTTP_HOST']}/auth.callback",
}.map{|k,v|
"#{k}=#{URI.encode v}"
}.join("&")
redirect "https://github.com/login/oauth/authorize?#{query}"
end

get '/auth.callback' do
code = params["code"]
halt 400, "bad request (code)" if code.to_s.empty?

## get oauth token
query = {
:body => {
:client_id => ENV["GITHUB_APP_ID"],
:client_secret => ENV["GITHUB_APP_SECRET"],
:code => code
},
:headers => {
"Accept" => "application/json"
}
}
res = HTTParty.post("https://github.com/login/oauth/access_token", query)
halt 500, "github auth error" unless res.code == 200
begin
token = JSON.parse(res.body)["access_token"] ## tokenを取得!
rescue
halt 500, "github auth error"
end
## sessionに保存するなど自由に
redirect '/'
end


取得したtokenはoctokitで使える。
require 'octokit'

client = Octokit::Client.new :oauth_token => token
user = client.user
user.avatar_url # アイコン画像

0

OAuth0.4.6とsystem_timerがおかしい

twitterにoauthしようとすると401が返ってくる

Faraday: you may want to install system_timer for reliable timeouts
/Users/sho/.rvm/gems/ruby-1.8.7-p358/gems/oauth-0.4.6/lib/oauth/consumer.rb:216:in `token_request': 401 Unauthorized (OAuth::Unauthorized)

なんか最新版のoauth 0.4.6がおかしい


0.4.5使ったら大丈夫だった。
source :rubygems

gem 'twitter'
gem 'oauth', '0.4.5'

faradayに原因があるのかと思ったが、どっちも最新版0.8.0使ってた。
詳しくは調べてない。

0

Twitterの地名なうbotを全blockするOAuthアプリ

http://shokai.mag.keio.ac.jp/block_nowbots/から使える。
(8月15日:URL変更しました

とりあえずSinatraとOAuthの組み合わせを試してみたかったので、DBは使わないものを作りたかった。取得したOAuthのtokenなどはその場で捨てている。blockコマンドを送る権限だけを一時的に委譲してもらう。


以下技術的なことのメモ。
■OAuthアプリの登録
まずhttp://twitter.com/oauth_clientsでアプリを登録し、consumer keyとconsumer secretを取得する。
で、下のコード中のCONSUMER_KEY, CONSUMER_SECRETを書き換える。


■必要なライブラリ
必要なgemをインストールする。最新版にした。

sudo gem install oauth twitter sinatra
それぞれ0.3.5, 0.6.13, 0.9.4がインストールされた。
twitterは内部でoauthに依存していて、oauthはバージョン毎に関数がけっこう変わっている。このバージョンの組み合わせなら動く。


■動かす
そして起動。
ruby block-nowbots.rb -p 2692 -s mongrel
thinだと複数クライアントから同時にアクセスした時1クライアントずつしか対応してくれなかったんだけど、mongrelは全クライアントに同時に応答してくれた。mongrelにいつのまにかそういう機能がついたのか、sinatraのバグでthinがthread処理されないのかはよくわからない。
sinatraもrailsと同じくデプロイまわりを工夫した方がよさそう。passenger使うのがいいのかな?


参考:


block-nowbots.rb
#!/usr/bin/env ruby
require 'rubygems'
require 'sinatra'
require 'oauth'
require 'twitter'

HOST_AND_PORT = "localhost:2692"

CONSUMER_KEY = "your-consumer-key"
CONSUMER_SECRET = "your-consumer-secret"

BOTS = 'nishinipporinow,nippori_now,kanda_now,tabata_now,sugamo_now,komagome_now,shinokubo_now,mejiro_now,takadanobabanow,okachimachi_now,uguisudani_now,ikebukuro_now,otsuka_now,akiba_now,tokyo_now,harajuku_now,shibuya_now,shibuya_now,yoyogi_now,shinjuku_now,ebisu_now'

set :sessions, true
def consumer
  OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET,
                      :site => "http://twitter.com")
end

template :layout do
  '<html>
     <body>
       <h1>地名なうbotを全blockするOAuthアプリ</h1>
       <%= yield %>
       <hr style="margin-top: 100px" />
       <a href="http://shokai.org">http://shokai.org</a><br /><a href="http://shokai.org/blog/archives/4698">解説</a>
     </body>
   </html>'
end

get '/' do
  @request_token = consumer.get_request_token(:oauth_callback => "http://#{HOST_AND_PORT}/auth")
  session[:request_token] = @request_token.token
  session[:request_token_secret] = @request_token.secret
  erb %{
    <p><%= BOTS %><br />をblock設定します</p>
    <p>OAuth認証してください</p>
    <a href="<%= @request_token.authorize_url %>">認証する!</a>
  }
end

get '/auth' do

  @request_token = OAuth::RequestToken.new(consumer,
                                          session[:request_token],
                                          session[:request_token_secret])
  @access_token = @request_token.get_access_token({},
                                                  :oauth_token => params[:oauth_token],
                                                  :oauth_verifier => params[:oauth_verifier])
  session[:access_token] = @access_token.token
  session[:access_token_secret] = @access_token.secret

  erb %{
    <p>認証成功</p>
    <p><a href="/block_now_bots">地名なうbotをblockする</a>(全部で<%= BOTS.split(",").size*3 %>秒くらいかかる)</p>
    <a href='/'>戻る</a>
  }
end

get '/block_now_bots' do
  oauth = Twitter::OAuth.new(CONSUMER_KEY, CONSUMER_SECRET)
  oauth.authorize_from_access(session[:access_token], session[:access_token_secret])
  twit = Twitter::Base.new(oauth)
  BOTS.split(",").each{|bot|
    twit.block(bot)
    puts "block #{bot}"
    sleep 3
  }
  erb %{
    <p>たぶんblockしました</p>
    <p><a href="http://twitter.com/yamanote_now/following">確認</a></p>
  }
end