SinatraでTwitterのWebアプリを作るための学習1

参考アプリで学習その1

まず twitter oauth example を使ってみます。
右側にある「Download Gist」を押して tar.gz ファイルをダウンロードし、展開します。
そして views ディレクトリを作成して、「views_index.erb」ファイルをそこへ移動して「index.erb」にリネームします(これをしないとアプリが動きませんorz)。

まずTwitterにアプリ登録

Twitter Application Management
でアプリケーション登録します。必要項目は

  • アプリ名
  • アプリの説明(認証画面で説明として表示される。短いとダメ。)
  • アプリURL(認証画面からリンクされる)
  • Callback URL(Webアプリを置くURL)

です。
この参考アプリを実行するのは自分の Mac なので、Callback URL は http://127.0.0.1 に設定しました。http://localhost では受け付けられませんでした(参考:TwitterアプリケーションのコールバックURLにlocalhostを指定する - sorry, uninuplemented)。
登録できたら「Keys and Access Tokens」に表示される「Application Settings」のうち

  • Consumer Key (API Key)
  • Consumer Secret (API Secret)

を控えておきます。

参考アプリ設定

さきほど控えた key/secret を以下の形式でコピペします。

TWITTER_CONSUMER_KEY="your_consumer_key"
TWITTER_CONSUMER_SECRET="your_consumer_secret"

これを .env というファイル名で参考アプリと同じディレクトリに保存します。

参考アプリ起動

$ bundle install
$ ruby app.rb

または

$ bundle exec ruby app.rb

で動きます。

ruby app.rb で起動

ログ:

$ ruby app.rb 
ENV['TWITTER_CONSUMER_KEY']: "your_consumer_key"
== Sinatra/1.4.5 has taken the stage on 4567 for development with backup from Thin
Thin web server (v1.6.3 codename Protein Powder)
Maximum connections set to 1024
Listening on localhost:4567, CTRL+C to stop
ブラウザでアクセス

続いてブラウザを立ち上げて http://localhost:4567 へアクセスします。
するとTwitterの認証画面
https://api.twitter.com/oauth/authenticate
へ転送されます。
ログ:

127.0.0.1 - - [02/Feb/2015:16:17:38 +0900] "GET / HTTP/1.1" 302 - 0.0038
I, [2015-02-02T16:17:38.966534 #2937]  INFO -- omniauth: (twitter) Request phase initiated.
127.0.0.1 - - [02/Feb/2015:16:17:39 +0900] "GET /auth/twitter HTTP/1.1" 302 105 0.6045
認証ボタンを押す

認証ボタンをクリックすると参考アプリ側へ戻り、

が表示されます。
ログ:(Callback URLに含まれる oauth_token と oauth_verifier は代えました)

I, [2015-02-02T16:18:49.221562 #2937]  INFO -- omniauth: (twitter) Callback phase initiated.
127.0.0.1 - - [02/Feb/2015:16:18:51 +0900] "GET /auth/twitter/callback?oauth_token=OAUTH_TOKEN&oauth_verifier=OAUTH_VERIFIER HTTP/1.1" 302 - 2.4810
127.0.0.1 - - [02/Feb/2015:16:18:52 +0900] "GET / HTTP/1.1" 200 83859 0.6584
リロード

リロードすると当然ながらタイムラインの内容が新しくなりました。
ログ:

127.0.0.1 - - [02/Feb/2015:16:26:14 +0900] "GET / HTTP/1.1" 200 137438 0.8190
Logout

ログアウトしても、自動認証してタイムライン表示画面まで進んでしまいます。
(追記:Twitterアプリの設定で「Allow this application to be used to Sign in with Twitter」にチェックを付けていたためでした。)
ログ:(Callback URLに含まれる oauth_token と oauth_verifier は先ほどと違う文字列になりました)

127.0.0.1 - - [02/Feb/2015:16:29:24 +0900] "GET /logout HTTP/1.1" 302 - 0.0011
127.0.0.1 - - [02/Feb/2015:16:29:24 +0900] "GET / HTTP/1.1" 302 - 0.0009
I, [2015-02-02T16:29:24.928731 #2937]  INFO -- omniauth: (twitter) Request phase initiated.
127.0.0.1 - - [02/Feb/2015:16:29:25 +0900] "GET /auth/twitter HTTP/1.1" 302 105 0.9783
I, [2015-02-02T16:29:26.848783 #2937]  INFO -- omniauth: (twitter) Callback phase initiated.
127.0.0.1 - - [02/Feb/2015:16:29:28 +0900] "GET /auth/twitter/callback?oauth_token=OAUTH_TOKEN2&oauth_verifier=OAUTH_VERIFIER2 HTTP/1.1" 302 - 1.2578
127.0.0.1 - - [02/Feb/2015:16:29:28 +0900] "GET / HTTP/1.1" 200 127780 0.7983

なお、 https://twitter.com/settings/applications でアプリの認証を切っておけば自動認証を止められます。

先にアプリの認証を切ってリロードしたら

エラーになります。ブラウザ画面は

Internal server error

のみ。
ログ:(gem へのパスを//に省略しています)

Twitter::Error::Unauthorized - Invalid or expired token:
	//gems/twitter-5.13.0/lib/twitter/rest/response/raise_error.rb:15:in `on_complete'
	//gems/faraday-0.9.1/lib/faraday/response.rb:9:in `block in call'
	//gems/faraday-0.9.1/lib/faraday/response.rb:57:in `on_complete'
	//gems/faraday-0.9.1/lib/faraday/response.rb:8:in `call'
	//gems/faraday-0.9.1/lib/faraday/request/url_encoded.rb:15:in `call'
	//gems/faraday-0.9.1/lib/faraday/request/multipart.rb:14:in `call'
	//gems/twitter-5.13.0/lib/twitter/rest/request/multipart_with_file.rb:17:in `call'
	//gems/faraday-0.9.1/lib/faraday/rack_builder.rb:139:in `build_response'
	//gems/faraday-0.9.1/lib/faraday/connection.rb:377:in `run_request'
	//gems/faraday-0.9.1/lib/faraday/connection.rb:140:in `get'
	//gems/twitter-5.13.0/lib/twitter/rest/request.rb:33:in `perform'
	//gems/twitter-5.13.0/lib/twitter/rest/utils.rb:51:in `perform_request'
	//gems/twitter-5.13.0/lib/twitter/rest/utils.rb:96:in `perform_request_with_objects'
	//gems/twitter-5.13.0/lib/twitter/rest/utils.rb:81:in `perform_get_with_objects'
	//gems/twitter-5.13.0/lib/twitter/rest/timelines.rb:113:in `home_timeline'
	app.rb:51:in `block in <main>'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1603:in `call'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1603:in `block in compile!'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:966:in `[]'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:966:in `block (3 levels) in route!'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:985:in `route_eval'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:966:in `block (2 levels) in route!'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1006:in `block in process_route'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1004:in `catch'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1004:in `process_route'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:964:in `block in route!'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:963:in `each'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:963:in `route!'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1076:in `block in dispatch!'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1058:in `block in invoke'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1058:in `catch'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1058:in `invoke'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1073:in `dispatch!'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:898:in `block in call!'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1058:in `block in invoke'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1058:in `catch'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1058:in `invoke'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:898:in `call!'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:886:in `call'
	//gems/omniauth-1.2.2/lib/omniauth/strategy.rb:186:in `call!'
	//gems/omniauth-1.2.2/lib/omniauth/strategy.rb:164:in `call'
	//gems/omniauth-1.2.2/lib/omniauth/builder.rb:59:in `call'
	//gems/rack-protection-1.5.3/lib/rack/protection/xss_header.rb:18:in `call'
	//gems/rack-protection-1.5.3/lib/rack/protection/base.rb:49:in `call'
	//gems/rack-protection-1.5.3/lib/rack/protection/base.rb:49:in `call'
	//gems/rack-protection-1.5.3/lib/rack/protection/path_traversal.rb:16:in `call'
	//gems/rack-protection-1.5.3/lib/rack/protection/json_csrf.rb:18:in `call'
	//gems/rack-protection-1.5.3/lib/rack/protection/base.rb:49:in `call'
	//gems/rack-protection-1.5.3/lib/rack/protection/base.rb:49:in `call'
	//gems/rack-protection-1.5.3/lib/rack/protection/frame_options.rb:31:in `call'
	//gems/rack-1.6.0/lib/rack/session/abstract/id.rb:225:in `context'
	//gems/rack-1.6.0/lib/rack/session/abstract/id.rb:220:in `call'
	//gems/rack-1.6.0/lib/rack/logger.rb:15:in `call'
	//gems/rack-1.6.0/lib/rack/commonlogger.rb:33:in `call'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:217:in `call'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:210:in `call'
	//gems/rack-1.6.0/lib/rack/head.rb:13:in `call'
	//gems/rack-1.6.0/lib/rack/methodoverride.rb:22:in `call'
	//gems/sinatra-1.4.5/lib/sinatra/show_exceptions.rb:21:in `call'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:180:in `call'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:2014:in `call'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1478:in `block in call'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1788:in `synchronize'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1478:in `call'
	//gems/thin-1.6.3/lib/thin/connection.rb:86:in `block in pre_process'
	//gems/thin-1.6.3/lib/thin/connection.rb:84:in `catch'
	//gems/thin-1.6.3/lib/thin/connection.rb:84:in `pre_process'
	//gems/eventmachine-1.0.4/lib/eventmachine.rb:1046:in `call'
	//gems/eventmachine-1.0.4/lib/eventmachine.rb:1046:in `block in spawn_threadpool'
Unexpected error while processing request: undefined method `join' for #<String:0x007fc1933d8310>
	//gems/sinatra-1.4.5/lib/sinatra/show_exceptions.rb:37:in `rescue in call'
	//gems/sinatra-1.4.5/lib/sinatra/show_exceptions.rb:21:in `call'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:180:in `call'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:2014:in `call'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1478:in `block in call'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1788:in `synchronize'
	//gems/sinatra-1.4.5/lib/sinatra/base.rb:1478:in `call'
	//gems/thin-1.6.3/lib/thin/connection.rb:86:in `block in pre_process'
	//gems/thin-1.6.3/lib/thin/connection.rb:84:in `catch'
	//gems/thin-1.6.3/lib/thin/connection.rb:84:in `pre_process'
	//gems/eventmachine-1.0.4/lib/eventmachine.rb:1046:in `call'
	//gems/eventmachine-1.0.4/lib/eventmachine.rb:1046:in `block in spawn_threadpool'

スクリプトを読む

スクリプトの意味はコメントしています。

  • app.rb
# 必要ライブラリの読み込み
require 'sinatra'
require 'omniauth-twitter'
require 'twitter'
require 'json'

# .env ファイルの読み込み(dotenv gem を使うと疑似環境変数として読み込める)
require 'dotenv'
Dotenv.load
puts "ENV['TWITTER_CONSUMER_KEY']: #{ENV['TWITTER_CONSUMER_KEY']}"

# Sinatra で使う Web サーバを thin に指定
set :server, :thin
# configure ブロックの `enable sessions` で有効になるセッションの秘密鍵を指定
# `enable sessions` を`set :sessions, secret: DateTime.now.to_s` にして同様のはず
set :session_secret, DateTime.now.to_s

# configure ブロックは起動時に1回だけ実行される
configure do
# セッションを有効にする(Cookie にセッション情報を保持)
  enable :sessions
# OmniAuth の設定(下にオプションについて記載しました)
  use OmniAuth::Builder do
# OmniAuth Twitter を使う
    provider :twitter, ENV['TWITTER_CONSUMER_KEY'], ENV['TWITTER_CONSUMER_SECRET']
  end
end

# ヘルパーメソッドの定義(ヘルパーメソッド:get/before などのルーティングハンドラ内で扱えるメソッド)
helpers do
# ログインしているか確認
  def logged_in?
    session[:twitter_oauth]
  end

# Twitter クライアントのインスタンスを作成
  def twitter
    Twitter::REST::Client.new do |config|
      config.consumer_key        = ENV['TWITTER_CONSUMER_KEY']
      config.consumer_secret     = ENV['TWITTER_CONSUMER_SECRET']
      config.access_token        = session[:twitter_oauth][:token]
      config.access_token_secret = session[:twitter_oauth][:secret]
    end
  end
end

# ルーティングハンドラ実行前に行う処理
before do
# パスが認証関連であれば次のルーティングハンドラへ
  pass if request.path_info =~ /^\/auth\//
# 認証していなければリダイレクト
  redirect to('/auth/twitter') unless logged_in?
end

# 以下はルーティングハンドラでパス毎の処理を記載

# Twitter 認証サイトから戻ってきたパス
get '/auth/twitter/callback' do
# 認証出来ていれば env['omniauth.auth'][:credentials] の返値に OAuth Token/Secret が含まれる(つまり nil じゃない)
  session[:twitter_oauth] = env['omniauth.auth'][:credentials]
# ルートへリダイレクト
  redirect to('/')
end

# Twitter 認証サイトから認証失敗で戻ってきたパス
get '/auth/failure' do
end

# ルートパス
get '/' do
# @auth に OAuth Token/Secret を設定
  @oauth = session[:twitter_oauth]
# ホームタイムライン(20ツイート)を取得
  @timeline = twitter.home_timeline
# index.erb テンプレートで表示
  erb :index
end

# ログアウトのパス
get '/logout' do
# session (つまり Cookie)を消去
  session.clear
# ルートへリダイレクト
  redirect to('/')
end
  • views/index.erb
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
  </head>
  <body>
    <a href="/logout">Logout</a><br/>
    <hr/>
    <h2>Access Token</h2>
<!-- OAuth Token/Secret を表示 -->
    <pre><%= JSON.pretty_generate(@oauth) %></pre>
    <hr/>
    <h2>Home Timeline</h2>
<!-- ホームタイムラインの各ツイートを表示 -->
    <% @timeline.each do |tweet| %>
      <!--<pre><%= JSON.pretty_generate(tweet.attrs) %></pre>-->
      <p><%= tweet.text %> by @<%= tweet.user.screen_name %></p>
    <% end %>
  </body>
</html>

インスタンス変数の一例

ここで、env の中身を見てみましょう。
認証から戻った /auth/twitter/callback での値です。

env.keys
#=> [
  "SERVER_SOFTWARE",
  "SERVER_NAME",
  "rack.input",
  "rack.version",
  "rack.errors",
  "rack.multithread",
  "rack.multiprocess",
  "rack.run_once",
  "REQUEST_METHOD",
  "REQUEST_PATH",
  "PATH_INFO",
  "QUERY_STRING",
  "REQUEST_URI",
  "HTTP_VERSION",
  "HTTP_HOST",
  "HTTP_CONNECTION",
  "HTTP_ACCEPT",
  "HTTP_USER_AGENT",
  "HTTP_ACCEPT_ENCODING",
  "HTTP_ACCEPT_LANGUAGE",
  "HTTP_COOKIE",
  "GATEWAY_INTERFACE",
  "SERVER_PORT",
  "SERVER_PROTOCOL",
  "rack.url_scheme",
  "SCRIPT_NAME",
  "REMOTE_ADDR",
  "async.callback",
  "async.close",
  "sinatra.commonlogger",
  "rack.logger",
  "rack.session",
  "rack.session.options",
  "rack.request.cookie_hash",
  "rack.request.cookie_string",
  "rack.session.unpacked_cookie_data",
  "omniauth.strategy",
  "omniauth.origin",
  "omniauth.params",
  "rack.request.query_string",
  "rack.request.query_hash",
  "omniauth.auth",
  "sinatra.route"
]
env
#=> https://gist.github.com/riocampos/613373cd4cade1a415ea#file-env

env のうち、認証関連の値が入っている env['omniauth.auth'] を見てみます。
なお、 env['omniauth.auth'] の各キーについては Auth Hash Schema/Schema (1.0 and Later) · intridea/omniauth Wiki に列挙されています。

env['omniauth.auth']
#=>{
  "provider"=>"twitter",
  "uid"=>"56997313",
  "info"=>
  {
    "nickname"=>"riocampos",
    "name"=>"riocampos(りおかんぽす)",
    "location"=>"Osaka, Japan",
    "image"=>
    "http://pbs.twimg.com/profile_images/555013502025216000/InbG3y6I_normal.jpeg",
    "description"=>"plz call me rio☆ favorite: cycle road race & figure skate, Mac!, iPhone!, Ruby, Fine-Art, Science, Black/Green Tea. | Gender:♂",
    "urls"=>
    {
      "Website"=>"http://t.co/KlG79Y3tGU",
      "Twitter"=>"https://twitter.com/riocampos"
    }
  },
  "credentials"=>
  {
    "token"=>"OAUTH_TOKEN",
    "secret"=>"OAUTH_TOKEN_SECRET"
  },
  "extra"=>
  {
    "access_token"=>
    #<OAuth::AccessToken:0x007fbfa3e1cf48
      @consumer=
        #<OAuth::Consumer:0x007fbfa3dfa808
        @http=#<Net::HTTP api.twitter.com:443 open=false>,
        @http_method=:post,
        @key="consumer_key",
        @options=
        {
          :signature_method=>"HMAC-SHA1",
          :request_token_path=>"/oauth/request_token",
          :authorize_path=>"/oauth/authenticate",
          :access_token_path=>"/oauth/access_token",
          :proxy=>nil,
          :scheme=>:header,
          :http_method=>:post,
          :oauth_version=>"1.0",
          :site=>"https://api.twitter.com"
        },
        @secret="consumer_secret",
        @uri=#<URI::HTTPS:0x007fbfa3e1c188 URL:https://api.twitter.com>
        >,
      @params=
      {
        :oauth_token=>"OAUTH_TOKEN,
        "oauth_token"=>"OAUTH_TOKEN",
        :oauth_token_secret=>"OAUTH_TOKEN_SECRET",
        "oauth_token_secret"=>"OAUTH_TOKEN_SECRET",
        :user_id=>"56997313",
        "user_id"=>"56997313",
        :screen_name=>"riocampos",
        "screen_name"=>"riocampos"
      },
      @response=#<Net::HTTPOK 200 OK readbody=true>,
      @secret="OAUTH_TOKEN_SECRET",
      @token="OAUTH_TOKEN"
    >,
    "raw_info"=>
    {
      "id"=>56997313,
      "id_str"=>"56997313",
      "name"=>"riocampos(りおかんぽす)",
      "screen_name"=>"riocampos",
      "location"=>"Osaka, Japan",
      "profile_location"=>nil,
      "description"=>"plz call me rio☆ favorite: cycle road race & figure skate, Mac!, iPhone!, Ruby, Fine-Art, Science, Black/Green Tea. | Gender:♂",
      "url"=>"http://t.co/KlG79Y3tGU",
      "entities"=>
      {
        "url"=>
        {
          "urls"=>
          [{
            "url"=>"http://t.co/KlG79Y3tGU",
            "expanded_url"=>"http://about.me/riocampos",
            "display_url"=>"about.me/riocampos",
            "indices"=>[0, 22]
          }]
        },
       "description"=>{"urls"=>[]}
      },
      "protected"=>false,
      "followers_count"=>2218,
      "friends_count"=>2439,
      "listed_count"=>196,
      "created_at"=>"Wed Jul 15 12:02:35 +0000 2009",
      "favourites_count"=>3092,
      "utc_offset"=>32400,
      "time_zone"=>"Tokyo",
      "geo_enabled"=>true,
      "verified"=>false,
      "statuses_count"=>222965,
      "lang"=>"en",
      "contributors_enabled"=>false,
      "is_translator"=>false,
      "is_translation_enabled"=>false,
      "profile_background_color"=>"48E0B7",
      "profile_background_image_url"=>"http://pbs.twimg.com/profile_background_images/378800000158576090/WjPzbAXY.jpeg",
      "profile_background_image_url_https"=>"https://pbs.twimg.com/profile_background_images/378800000158576090/WjPzbAXY.jpeg",
      "profile_background_tile"=>true,
      "profile_image_url"=>"http://pbs.twimg.com/profile_images/555013502025216000/InbG3y6I_normal.jpeg",
      "profile_image_url_https"=>"https://pbs.twimg.com/profile_images/555013502025216000/InbG3y6I_normal.jpeg",
      "profile_banner_url"=>"https://pbs.twimg.com/profile_banners/56997313/1420302457",
      "profile_link_color"=>"009999",
      "profile_sidebar_border_color"=>"FFFFFF",
      "profile_sidebar_fill_color"=>"EFEFEF",
      "profile_text_color"=>"333333",
      "profile_use_background_image"=>true,
      "default_profile"=>false,
      "default_profile_image"=>false,
      "following"=>false,
      "follow_request_sent"=>false,
      "notifications"=>false
    }
  }
}

ついでに response, params, request の中身も出してみましょう。
これらもまた認証から戻った /auth/twitter/callback での値です。

response
#=> #<Sinatra::Response:0x007fd0bd582cb8
  @block=nil,
  @body=[],
  @chunked=false,
  @header={"Content-Type"=>nil},
  @length=0,
  @status=200,
  @writer=#<Proc:0x007fd0bd582b28@/<gem_path>/gems/rack-1.6.0/lib/rack/response.rb:30 (lambda)>
>
params
#=> {"oauth_token"=>"OAUTH_TOKEN",
 "oauth_verifier"=>"OAUTH_VERIFIER"}
request
#=> #<Sinatra::Request:0x007f843abc9ad8
 @env={ 上記のenvの中身 },
 @params={ 上記のparamsの中身 }
>

つまり、 request インスタンスが一番大きいんですね。

pry> request.class
#=> Sinatra::Request
pry> ls request
#=> Rack::Request#methods: 
  GET              delete_param       params           scheme         
  POST             env                parseable_data?  script_name    
  []               form_data?         patch?           script_name=   
  []=              fullpath           path             session        
  accept_encoding  get?               path_info        session_options
  accept_language  head?              path_info=       ssl?           
  base_url         host               port             trace?         
  body             host_with_port     post?            trusted_proxy? 
  content_charset  ip                 put?             update_param   
  content_length   logger             query_string     url            
  content_type     media_type         referer          user_agent     
  cookies          media_type_params  referrer         values_at      
  delete?          options?           request_method   xhr?           
Sinatra::Request#methods: 
  accept   forwarded?   link?           safe?    unlink?
  accept?  idempotent?  preferred_type  secure?
instance variables: @env  @params

YARDへのリンクも張っておきます。

OmniAuth の設定について

基本的な動き

use OmniAuth::Builder すると、get /auth/twitter のルーティングを拾い、api.twitter.com やユーザとのやりとりの後、認証できれば /auth/twitter/callback、失敗すれば /auth/failure に帰ってきます。ユーザ情報その他は env['omniauth.auth'] に入ってます。

By default, OmniAuth will configure the path /auth/:provider. It is created by OmniAuth automatically for you, and you will start the auth process by going to that path.
Also by default, OmniAuth will return auth information to the path /auth/:provider/callback inside the Rack environment.
Also of note, by default, if user authentication fails on the provider side, OmniAuth will catch the response and then redirect the request to the path /auth/failure, passing a corresponding error message in a parameter named message.

ということで、参考アプリでは

before do
# パスが認証関連であれば次のルーティングハンドラへ
  pass if request.path_info =~ /^\/auth\//
# 認証していなければリダイレクト
  redirect to('/auth/twitter') unless logged_in?
end

このリダイレクト先のルーティングを受ける get ハンドラ

get '/auth/twitter'

が存在しなかったわけですね(OmniAuthが自動的に受けるから)。
そして /auth/twitter/callback で、返ってきたときの処理を行う、という流れ。

get '/auth/twitter/callback' do
# 認証出来ていれば env['omniauth.auth'][:credentials] の返値に OAuth Token/Secret が含まれる(つまり nil じゃない)
  session[:twitter_oauth] = env['omniauth.auth'][:credentials]
# ルートへリダイレクト
  redirect to('/')
end
Strategy パターン

OmniAuth は Strategy パターンを使って各種 OAuth サイトへの認証を行っています。 OAuth の作業自体は同じでも、各サイトによって内容が異なる。 TwitterFacebook では詳細部分が当然異なるわけです。その部分を Strategy パターンで振り分けて作業をしているようです。
参考:Ruby - Strategy パターン - Qiita
具体的に書くと、 OmniAuth::Strategies 以下のクラスに Strategy クラスを用意すれば OmniAuth 側から利用できます。 omniauth-twitter gem の場合は OmniAuth::Strategies::Twitter が使われます(もしも omniauth-github gem であれば OmniAuth::Strategies::GitHub 、omniauth-heroku であれば OmniAuth::Strategies::Heroku が使われます)。
Strategy のリストは

に列挙されています。

OmniAuth::Builder ブロックの provider メソッド
configure do
  enable :sessions
  use OmniAuth::Builder do
    provider :twitter, ENV['TWITTER_CONSUMER_KEY'], ENV['TWITTER_CONSUMER_SECRET']
  end
end

この provider メソッドにより、最終的に use OmniAuth::Strategies::Twitter に変換されます。

認証オプション

ただしオプションリストのうち secure_image_url, image_size は無効です。
例:

use OmniAuth::Builder do
  provider :twitter, "API_KEY", "API_SECRET",
    {
      :use_authorize => 'true', # Twitter app 設定で
                                # "Allow this application to be used to Sign in with Twitter"
                                # にチェックをしていても、認証画面を必ず出す
      :x_auth_access_type => 'write', # Twitter app 設定でアクセスレベルを writeにしていても
                                      # read に制限することができる
                                      # read 登録を write にすることはできない
      :authorize_params => {
        :force_login => 'true', # 一度強制的にログアウトさせる(非推奨)
        :lang => 'ja',          # ログイン画面の言語を指定する
        :screen_name => 'riocampos' # ログイン画面のデフォルトアカウント名
      }
    }
end
A simple authentication example in Sinatra

に載っていたスクリプトを若干手直し。 .env ファイルに Twitter アプリの key/secret を入れておくのは参考アプリと同じです。

require 'omniauth-twitter'
require 'sinatra'
require 'dotenv'
require 'pp'

Dotenv.load

use Rack::Session::Cookie
use OmniAuth::Builder do
  provider :twitter, ENV['TWITTER_CONSUMER_KEY'], ENV['TWITTER_CONSUMER_SECRET']
end

get '/' do
  <<-HTML
  <a href='/auth/twitter'>Sign in with Twitter</a>
  HTML
end

get '/auth/:name/callback' do
  auth = request.env['omniauth.auth']
  <<-HTML
  #{auth.pretty_inspect.gsub("\n", "<br>")}
  HTML
end

Haml で書いてみる

もう一度 index.erb を示しておきます。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
  </head>
  <body>
    <a href="/logout">Logout</a><br/>
    <hr/>
    <h2>Access Token</h2>
    <pre><%= JSON.pretty_generate(@oauth) %></pre>
    <hr/>
    <h2>Home Timeline</h2>
    <% @timeline.each do |tweet| %>
      <!--<pre><%= JSON.pretty_generate(tweet.attrs) %></pre>-->
      <p><%= tweet.text %> by @<%= tweet.user.screen_name %></p>
    <% end %>
  </body>
</html>

これを index.haml にしてみました。

!!! 5
%html
  %head
    %meta{charset: "UTF-8"}
  %body
    %a{href: "/logout"} Logout
    %br
    %hr
    %h2 Access Token
    %pre= JSON.pretty_generate(@oauth)
    %hr/
    %h2 Home Timeline
    - @timeline.each do |tweet|
      %p
        = tweet.text
        by
        = tweet.user.screen_name

もちろん require 'haml' して、さらに erb を呼ぶところを haml に変更する必要があります。

get '/' do
  @oauth = session[:twitter_oauth]
  @timeline = twitter.home_timeline
#  erb :index
  haml :index
end