第27回 #p4d 参加した:omniauth-twitter

前回の続き。おかげさまで、Iteration 周りは自力でも進められそうな感じになってきたので、Twitter でユーザーがログインする機能を作りたいとおもった。 omniauth-twitter を使えば楽にできるけど、楽に出来すぎてよくないので OAuthからちゃんとやった方がいいですよというアドバイスを頂き、どっちがいいのかなあと思っていたのだが、あまり時間もないので omniauth 使うことに。今回、@katton さんに教えていただいたのですが、 OAuth とは?というところからわかりやすく教えていただき、だいぶカバーされた感じなので、下記おすすめ書籍を読んで理解深めたい。
また、@katton さんは、リアルタイムでエディタでお題を書きながら、そのメモをエディタ上で見せながら進めてくださり、さらにそのメモを帰宅後送って下さるなど、大変ありがたく、またこの方法は教えるのに大変よい方法だと思った。頂いたメモをベースに追記する感じで、復習が進められた(下記は、その記録)。ありがとうございました!

0. OAuthとは

リソース保有者が、リソースプロバイダー(Twitterとか)に対して「アプリに自分の情報を教えても良い」と許可すること。
※ 今回はサーバサイドWebアプリケーションフローを採用。

1. twitterにアプリケーション登録

本番用と開発用両方を登録しておく
dev.twitter.com/apps

  • 本番用 Callback URL: domain/auth/twitter/callback
  • 開発用 Callback URL:(pow.cx を使った場合): APPNAME.dev/auth/twitter/callback

omniauth の callback URL は、/auth/[プロバイダ名]/callback


callback
get '/auth/:provider/callback', to: 'sessions#create'

2. Figaro使う

laserlemon/figaro · GitHub
なぜ?:アプリ固有の設定をapplication.ymlというカタチで保存、Figaro.env.xxxで読み込みができる。

インストール
gem 'figaro'


$ bundle

$ rails g figaro:install

config/application.yml に先ほどdev.twitter.com で登録したときの、key と secret を書いておく。

こうすることで、各プロバイダにアクセスする用の key 達が一元管理できる。
config/application.yml はインストール時に自動的に.gitignore に追加されるので、ソースコードを公開してもヒミツにしておけるのもナイスだ。

development:
  twitter_key: xxxxxxxxxxxxxxxxxxxxx
  twitter_secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

production:
  twitter_key: xxxxxxxxxxxxxxxxxxxxx
  twitter_secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Heroku

Herokuに デプロイするときは、この1行コマンド打てばOK


$ bundle exec rake figaro:heroku

3. Omniauth を使う

intridea/omniauth ・ GitHub
arunagw/omniauth-twitter ・ GitHub
※ omniauth-twitter のように、付属機能的な意味合いの場合はGem名の接続子が "-" になる

インストール

Gemfile

gem 'omniauth'
gem 'omniauth-twitter'


$ bundle install

設定ファイルをじぶんで作る

複数プロバイダを使用する場合は、別ファイルに設定を書いたりしないといけなくてめんどい。今回はTwitterだけなので1つのファイルでOK。
config/initializers/omniauth.rb
公式のUsageではこんな感じに、直接KEY と SECRET を書けとなっているが…

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, "CONSUMER_KEY", "CONSUMER_SECRET"
end

先ほど、Figaro を入れたおかげで、config/application.yml に書いたKEY 達をこのように設定から呼び出せるようになってる。ナイスだ。

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :developer unless Rails.env.production?
  provider :twitter, Figaro.env.twitter_key, Figaro.env.twitter_secret
end

4. Userモデルをつくる

必要なカラムを確認

omniauth-twitter の Authentication Hashを見ると、Twitterから取れる情報が一覧できる。いっぱい取れる情報があるんですねー。
今回は、ログインに使うくらいなので、名前とアイコンくらいがあればいいかなー。
uid, secret, token は必ず必要だよ。ってなわけでとりあえず必要なのは以下の5つとなる。

  • uid
  • name
  • nickname
  • image
  • token
  • secret


$ rails g model user uid name nickname image token secret
全部 string でいいので、並べるだけでOKだった。
uid は必須かつユニークにしておこう。
/db/migrate/20130815115856_create_users.rb

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :uid, null: false
      t.string :name
      t.string :nickname
      t.string :image
      t.string :token
      t.string :secret

      t.timestamps
    end
    add_index :users, :uid, unique: true
  end
end


$ rake db:migrate

5. OAuth認証をする

5-1. TwitterへのURLをつくる


= link_to 'Twitterでログイン', '/auth/twitter'
というリンクを仮にTOPのviewに作っておく

5-2. powインストール

Pow: Zero-configuration Rack server for Mac OS X
開発ディレクトリのシンボリックリンクを~/.pow に追加しておくだけで、http://APPNAME.dev というURLでアクセスできるというイカしたもの。みんな大好き37signals 製。CoffeeScript と Node.js でできてるらしい。シャレオツだね。
実際のPowの導入は、P4D時間内に間に合わず、帰宅後になったんだけど、少しハマった。


$ curl get.pow.cx | sh

$ cd ~/.pow
$ ln -s /path/to/myapp
だけで、http://APPNAME.dev
でアクセスできるはずなんだけど、

LoadError: no such file to load -- bundler/setup
っていうエラーが出てる。私の環境は、RVMでなくてrbenv(brew インストールでない)なのでおそらく Troubleshootingに書いてある
~/.powconfig に下記パスを追加する

export PATH="$HOME/.rbenv/shims:$HOME/.rbenv/bin:$PATH"
というのをやれば大丈夫なはず…あれ、動かない。。上記対処後アプリの再起動的なものも色々試みてだめだったので、一回powをUninstallしたのち再インストールしたら動いた。

5-3. Callbackを受け取って必要な情報をUserへ

logger.info で確認
app/controllers/session_controller.rb

  def create
    puts '==========='
    logger.info(request.env['omniauth.auth'])
    redirect_to '/'
  end

ログにTwitterからの情報っぽいものが出力されたら成功(puts '=========' は、ログの目印的に付加してる)

5-4. User.find_or_create_from_auth_hash の実装

この辺の半ばでP4Dはタイムアップになったので、@katton さんが書いて下さったメモを参考に、家で続きやる
app/models/users.rb に uidからuserを見つける -> なかったら新規にuser を作る という機能に必要なクラスメソッドを作る

  # find_or_create_by_oauth 既存のoauthを見つける -> 無かったら新しく作る 
  def self.find_or_create_by_oauth(auth)
    if user = User.find_by_oauth(auth)
      user
    else
      User.create_by_oauth(auth)
    end
  end

  # uid を使って 既存の user を見つける
  def self.find_by_oauth(auth)
    user = User.find_by_uid(auth['uid'])
    # user = User.find_by(uid: auth['uid']) # Rails4ではこう書ける
    if user
      user
    else
      nil
    end
  end

  # 新しく user を作る
  def self.create_by_oauth(auth)
    user = User.new
    user.uid = auth['uid']
    user.name = auth.info['name']
    user.nickname = auth.info['nickname']
    user.image = auth.info['image']
    user.token = auth.credentials['token']
    user.secret = auth.credentials['secret']
    user.save
    user
  end

同じく app/controllers/sessions_controller.rbにも authから ユーザーを見つけるクラスメソッドを追加
https://github.com/intridea/omniauth#integrating-omniauth-into-your-application

  def create
    @user = User.find_or_create_by_auth(auth_hash)
    #self.current_user = @user
    redirect_to '/'
  end

  protected

  def auth_hash
    request.env['omniauth.auth']
  end

current_user は まだないのでコメントアウトしておく。さきほど作った、Twitterへのリンクをクリックして確認してみる -> 認証ボタン押して戻ってきたら


$ rails c
User.find(:all)
[#
おー、ユーザー情報入ってるーヾ(*'ω'*)ノ゙

5-5. sessionにuser_idを保存して、「ログイン」をさせる

参考)https://speakerdeck.com/takai/login-form-from-scratch
app/controllers/sessions_controller.rb に session[:user_id] = @user.id を追加

  def create
    @user = User.find_or_create_by_oauth(auth_hash)
    #self.current_user = @user
    session[:user_id] = @user.id
    redirect_to '/'
  end
5-6. ログインの確認

app/controllers/application_controller.rbに current_user の メソッドを追加

  def current_user
    if session[:user_id]
      User.find(session[:user_id]) 
    else
      nil
    end

確認してみようー
app/controllers/sessions_controller.rb で loggers.info を書いて確認

  def create
		・
		・
		・
    logger.info("========current_user.name: #{current_user.name}")
    #self.current_user = @user
  end

※ 結局 この self.current_user の行は、別のところで current_user を定義したのでいらなさそげだ
ログに、Twitterの名前が出てきたら成功っぽい!

と、そんなわけで、一連のログイン機能のベースができたっぽいので、ここから、user と task や iteration を紐付けたり、ログインページを作ったりする。公開に一歩近づいた感じが嬉しい。

感想とか

  • omniauth 使うと簡単だと聞いたけど、私のレベルだとそれでもかなり苦労したので、結果 omniauth 使ってよかったのではないかと思う・・・
  • 今回も、@katton さんに大感謝だ・・・本当に、ありがとうございましたm(__)m
  • Pow便利だなー。Powいかすー。
  • 毎回、違うプログラマさんに教えていただくが、プログラマさんによってやり方も色々だったりするので、結果、色んな方法やアイデア、色んなツールが知れることになり、これはかなり素晴らしいのではないかと思った!(ありがたい・・・
  • find_or_create_by_auth はできたけど、これだけだとユーザーネームやアイコンを変えた時とかに更新されないので、それ用のメソッドがまた必要なんだろうなー
  • また、時間いっぱい教わってしまい、こちらからデザインの話とかすることができずに申し訳なす・・・
  • 今回、半分の時間で交代で区切っていただいたのだけど、やっぱり教わるとなると時間が足りずにフルに教わることになってしまう。
  • ので、前回教わった人は次回教えればいいんじゃないか、ってことにもなったので、次回はなにか教える側に回ります。