2014年7月5日土曜日

6-3 OAuthを利用して "Twitterでログイン" 機能を作る

OAuthとは

OAuthは、Twitter等の大手サービスを利用する認証方法。利用するサービスのパスワードを又貸しするようなことをせずに認証を実現できるので、手軽で現在は広く使われているんですってよ、奥さん。

Twitterアプリケーションの登録

p168に沿ってその通り登録。Application detailsのNameは重複が許されないとのことで、適当にふざけた名前を付けた。

Twitterアカウントでログインする機能の作成

とりあえず以下がやりたい。
ヘッダーにログインリンクを作る
クリックするとTwitter認証画面
Twitter認証画面で適切なユーザー名とパスワードの入力でログイン
ログイン中はログインリンクの代わりにログアウトのリンク
ログアウトをクリックするとログアウトする
認証に実際に使用するgemはOmniAuth。Twitterを認証に利用する場合は更にOmniAuth Twitterというgemが必要。
 今回はテキストに合わせるため、バージョンをpatchレベル以外は固定。ポリシーはアプリケーションにより決める。

 次にTwitter管理画面上のAPI keyとAPI secretをOmniAuthに設定する。Rails4.1からはconfig/secrets.ymlが設定ファイル。
default_twitter: &default_twitter
  twitter_api_key: 'tjEBXXXXXXXXXXXXXdjhBo'
  twitter_api_secret: 'mZKR0XXXXXXXXXXXXXXXXXXXXXX45e4fyO73nt'
development:
  secret_key_base: tjEBZXXXXXXXXXXXX4djhBo
  <<: *default_twitter
test:
  secret_key_base: mZKR0vXXXXXXXXXXXXXXXXXXu9t45e4fyO73nt
  <<: *default_twitter
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
  twitter_api_key: <%= ENV["TWITTER_CONSUMER_KEY"] %>
  twitter_api_secret: <%= ENV["TWITTER_CONSUMER_SECRET"] %>
 次、もう一つ。config/initializers/omniauth.rbを作成して下記を入れておく。
Rails.application.config.middleware.use OmniAuth::Builder do |
provider :twitter,
 Rails.application.secrets.twitter_api_key,
 Rails.application.secrets.twitter_api_secret|
end
  これで設定完成。説明はp173を参照。まだこのキーがいつどのように使われるかよくわかっていない。

 続いてログインユーザーのモデルを作成する。
rails g model user provider:string uid:string nickname:string image_url:string
マイグレーションファイルにNOT NULL制約とユニークインデックス追加しておく。これはなんだ。validationじゃなくて、ここに書くんだ。うーん。ちょっとわからんぞ。なんにしても、未記入は許しまへんで、ということですよね?
      t.string :provider, null: false
      t.string :uid, null: false
      t.string :nickname, null: false
      t.string :image_url, null: false
参考:

 で、マイグレーションする、と。

 続いては、Twiterにログインというリンクを作る。コードはp175を参照して、application.html.erbに書いた。認証先のリンクはOmniAuthの仕様で、/auth/:providerになる。
 この後サイトへアクセスしてログインボタンを押すと、見慣れたTwitterの「連携アプリの認証」ボタンの画面に移った。おー、なんか今っぽいすね。
 認証ボタンを押すと、まだ後処理が用意されていないためエラーになりました。ルーティングエラー。

 さて、ログイン兼ユーザー登録用のコントローラを作成します。
rails g controller sessions
 この辺のコントローラの名前とか、自分にセンスがなさそうなので心配。単数系にするときとかもどうもぼんやり。今回はユーザーを扱うので当然複数形ですよね。こういう単純な考え方がそもそもいけないのか。

 createアクションの作成。
class SessionsController < ApplicationController
def create
user = User.find_or_create_from_auth_hash(request.env['omniauth.auth'])
session[:user_id] = user.user_id
redirect_to root_path, notice: 'ログインしました'
end
end
OmniAuthによりrequest.env['omniauth.auth']にHashに似たOmniAuth::AuthHashが格納される。Twitterから渡されたユーザー情報やOAuthのアクセストークンも含まれる。
 find_or_create_from_auth_hashはまだ未定義。方針としては、引数に関連するユーザーがいればそれを返し、いなければ新規作成して返すという動きをさせる。
 最終的にはUser.find_or_create_from_auth_hashが返すUserのIDをセッションに格納することでログインしたという扱いにする。こういうの難しいわ〜。論理的に説明だけされるとボーッとなりますね。ちょいちょい具体的な話や面白話がないと。本が何倍にもなりそうですが。
 当然次はUser.find_or_create_from_auth_hashの実装。/models/user.rbで。
class User < ActiveRecord::Base
def self.find_or_create_from_auth_hash(auth_hash)
provider = auth_hash[:provider]
uid = auth_hash[:uid]
nickname = auth_hash[:info][:nickname]
image_url = auth_hash[:info][:image]
User.find_or_create_by(provider: provider, uid: uid) do |user|
user.nickname = nickname
user.image_url = image_url
end
end
end
 find_or_create_byはActiveRecordのメソッド。
 内容としては、引数で渡したproviderとuidを持つレコードが存在していればそのオブジェクトを返し、存在しなければprovider、uid、nickname、image_urlを設定してレコードを作成し、そのオブジェクトを返す、と。

 ルーティングの追加。
get '/auth/:provider/callback' => 'session#create'
  これはどういうことだ。/auth/:provider/callbackで帰ってくるってのは自明?あ、さっき出たルーティングエラーにアクセス先が/auth/twitter/callbackとは書かれているな。OAuthとやらの仕様か。それともTwitterの仕様?
 application.html.erbについてもp179の通り追記する。これでcreateアクションからの返り値により、ログインしましたというメッセージが表示されるようになる。できた。

 最後はログアウト処理。まずはdestroyアクションの追加。
def destroy
reset_session
redirect_to root_path, notice: 'ログアウトしました'
end
 次は、ログイン中は画面右上にログアウトのリンクを表示させたい。logged_in?メソッドを定義して、ログイン状態が把握できるようにする。
 application.html.erbではp180の通りlogged_in?の条件分岐でログイン/ログアウトを表示し分ける。ログアウトのリンク先のlogout_pathはあとでroutes.rbで設定する。
 logged_in?の定義はapplication_controller.rbで下記の通り。
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  helper_method :logged?
  private
  def logged_in?
  !!session[:user_id]
  end
end
 hyper_methodってなんだよ。コントローラのメソッドをビューでも使う場合に使うってよくわからない。

参考:

 あと、!!ってなに?基本過ぎ?

参考:

 はぁ、!が論理演算のnotだって。すると、sessionがユーザーidを持っていれば!によってfalse、もう一つの!でtrue、で結果としてこれがログイン状態になるんだ。頭いいねぇ。感心した。
 未ログインでsessionがnullなら!でtrue、もう一回!でfalse。だから未ログイン。

 最後にルーティングを追加。
 get '/logout' => 'sessions#destroy', as: :logout
 プログラミングでごく一般的なことがわかっていないから、どの説明も本筋から外れたところで新たな発見がたくさんある。ありすぎて調べ続けているとなにやっていたかわからなくなったりする。

 この深夜に仕事のLineがきて、確かに自分が悪いところもあるけどものすごく不快な気持ちになってしまった。寝よう寝よう。

0 件のコメント:

コメントを投稿