寝て起きて寝て

プログラミングが出来ない情報系のブログ

Ruby on Railsでユーザとパスワードとtwitter認証でログインできるようにする

実行環境


Rails 4.2.6

ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]

作りたいもの


今回はdeviseを使いログイン画面を作成していく。

ただ今回はdeviseがデフォルトで持っている機能の"メールアドレス"でログインする機能をなくしユーザー名、パスワードだけでアカウントを作成しログインすることができるようにする。 ついでにtwitterアカウントでもログインできるようにする。

細かい設定とかはここではやらない。最低限できるようにするだけ。

準備


rails new board -TB --skip-turbolinks

Tはテストを作らない Bはbundle installのスキップ

アプリケーション内のgemファイルに以下のように記述

gem 'twitter-bootstrap-rails','3.2.2'
gem 'omniauth-twitter','1.2.1'
gem 'slim-rails','3.1.0'
gem 'devise','4.1.1'
gem 'html2slim','0.2.0'

#デバッグ用
gem 'pry'
gem 'pry-rails'

そしたらbundle installする

※今回slimの記法に慣れたいと思ったのでinstallしてます。

bootstrapを使うには以下のコマンドを実行しておく

rails generate bootstrap:install static

deviseの設定


rails g devise:install

を実行以下の様なメッセージが出力される

      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
===============================================================================

Some setup you must do manually if you haven't yet:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

  4. If you are deploying on Heroku with Rails 3.2 only, you may want to set:

       config.assets.initialize_on_precompile = false

     On config/application.rb forcing your application to not access the DB
     or load models when precompiling your assets.

  5. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

===============================================================================

デフォルトURLの指定


config/environments/development.rbを編集していく。

上のメッセージで書いてあった

config.action_mailer.default_url_options = { host: 'localhost:3000' }

を追加で記述(localhostのところはサーバでやってるならアドレスに変えればできる)

コントローラーの作成


rails g controller home index

モデルの作成


rails g devise User

生成されたマイグレーションファイルは以下の通り

class DeviseCreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      t.integer  :sign_in_count, default: 0, null: false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

モデルはこんな感じ

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
end

この辺の設定は後で変える。

次にroot_urlの設定をしておく

http://localhost:3000/にアクセスした際に表示されるページを指定する。 これをやっとかないと上手く動いてくれない

config/routes.rb

Rails.application.routes.draw do
  devise_for :users
  get 'home/index'
  root 'home#index'
end

ビューの再設定


今回ビューはslimを使うので以下のように変更する

app/views/home/index.html.slim

- if user_signed_in?
    p=current_user.email
    =link_to "Settings", edit_user_registration_path
    =link_to "Logout", destroy_user_session_path, method: :delete
- else
    =link_to "Sign up", new_user_registration_path
    =link_to "Login", new_user_session_path

テンプレートのerbは名前をapp/views/layouts/application.html.slimに変更して以下のように記述し直す

doctype html
html
  head
    title board app
    meta name="viewport" content="width=device-width, initial-scale=1.0"
    = stylesheet_link_tag    "application", media: 'all'
    = javascript_include_tag "application"
    = csrf_meta_tags

  body
    - if notice
      p.alert.alert-success = notice
    - if alert
      p.alert.alert-danger = alert
    = yield

一度ここでマイグレートして起動の確認をしておく

rake db:migrate
rails s -b 0.0.0.0

確認ができたら外見をいじるためにもDeviseのViewを作成する 以下のコマンドを実行

rails g devise:views

すると以下の様なファイルが生成される

app/views/devise/shared/_links.html.erb (リンク用パーシャル)
app/views/devise/confirmations/new.html.erb (認証メールの再送信画面)
app/views/devise/passwords/edit.html.erb (パスワード変更画面)
app/views/devise/passwords/new.html.erb (パスワードを忘れた際、メールを送る画面)
app/views/devise/registrations/edit.html.erb (ユーザー情報変更画面)
app/views/devise/registrations/new.html.erb (ユーザー登録画面)
app/views/devise/sessions/new.html.erb (ログイン画面)
app/views/devise/unlocks/new.html.erb (ロック解除メール再送信画面)
app/views/devise/mailer/confirmation_instructions.html.erb (メール用アカウント認証文)
app/views/devise/mailer/reset_password_instructions.html.erb (メール用パスワードリセット文)
app/views/devise/mailer/unlock_instructions.html.erb (メール用ロック解除文)

生成されたファイルは拡張子が.erbなのでerb2slimを使ってslimに変更する

erb2slim -d app/views/devise/

メールアドレスで登録できないようにする


dbのマイグレートファイルを以下のように追記と変更を行う

    #上の方にある
    #t.string :email,             null: true, default: ""    #コメントアウト
    t.string :username,          null: false, default: ""  #追記

    #下の方にある
    #add_index :users, :email,                unique: true    #コメントアウト
    add_index :users, :username,            unique: false    #追記

編集が終わったら

rake db:migrate:resetを行い、テーブルの作りなおしを行う

次にuser.rbにバリデーションの追加を行う

vim app/models/user.rb

validates :username, presence: true, length: { maximum: 20 }
  #usernameを利用してログインするようにオーバーライド
  def self.find_first_by_auth_conditions(warden_conditions)
    conditions = warden_conditions.dup
    if login = conditions.delete(:login)
      #認証の条件式を変更する
      where(conditions).where(["username = :value", { :value => username }]).first
    else
      where(conditions).first
    end
  end

  def email_required?
    false
  end

  def email_changed?
    false
  end

String Parametersの設定


以下を追記

app/controllers/application_controller.rb

  before_action :configure_permitted_parameters, if: :devise_controller?
  protected
  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:sign_up) << :username
    devise_parameter_sanitizer.for(:sign_in) << :username
    devise_parameter_sanitizer.for(:account_update) << :username
  end

config/initializers/devise.rb

#追記
config.authentication_keys = [:username]

補足 configを弄りたくない人はuserモデルのdevise に

:authentication_keys => [:username]

を追記するのでもいける

ビューの再設定


app/views/home/index.html.slim をemailからusernameに変更

p=current_user.email

↓

p=current_user.username

次に

app/views/devise/registrations/new.html.slim と app/views/devise/sessions/new.html.slim

emailのフィールドを全部消してから以下を記述

  .field
    = f.label :username
    br
    = f.text_field :username, autofocus: true

rails s -b 0.0.0.0

で起動してからsigin_upとloginをしてみる

Twitter認証機能の追加


Twitter認証を行うにはAPIキーなどが必要になるのでここをみて作っておく

今回はAPIなどの設定ファイルをdirenvで管理するためここで設定しておく

まずrailsTwitter認証が行えるようにUserモデルを以下のように編集する

app/models/user.rb

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable,
         :omniauthable,omniauth_providers: [:twitter]

  validates :username, presence: true, length: { maximum: 20 }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~中略~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
end

次に環境変数に作ったAPIキーをrailsで使用する準備をする

devise.rbを編集

最後の行付近に記述する

config/initializers/devise.rb

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
config.omniauth :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
end

これで準備ができたのでdirenvを使う

direnv edit .

エディタが開かれるのでexport文を書く

export TWITTER_KEY="xxxxxxxxxxxxxxxxx"
export TWITTER_SECRET="xxxxxxxxxxxxxxxxxxxxxx"

入力し終わると以下のように表示される

direnv: loading .envrc
direnv: export +TWITTER_KEY +TWITTER_SECRET

一度rails sで一度twitter認証をしてみる

Unknown actionと表示されればおk

マイグレートファイルに下の二行を追加

db/migrate/20160628063208_devise_create_users.rb

      t.string :provider
      t.string :uid

rake db:migrate:resetでDBの作り直し

コールバック用のコントローラー作成

rails g controller sns_callbacks

中身は以下のようになっている

class SnsCallbacksController < Devise::OmniauthCallbacksController
    def twitter
        @user = User.from_omniauth(request.env["omniauth.auth"].except("extra"))

        if @user.persisted?
            flash.notice = "ログインしました!"
            sign_in_and_redirect @user
        else
            session["devise.user_attributes"] = @user.attributes
            redirect_to new_user_registration_url
        end
    end
end

userモデルに追記

  def self.from_omniauth(auth)
        where(provider: auth["provider"], uid: auth["uid"]).first_or_create do |user|
            user.provider = auth["provider"]
            user.uid = auth["uid"]
            user.username = auth["info"]["nickname"]
        end
  end

  def self.new_with_session(params, session)
    if session["devise.user_attributes"]
       new(session["devise.user_attributes"], without_protection: true) do |user|
         user.attributes = params
         user.valid?
       end
    else
       super
    end
  end

これをやっておかないとuidやproviderなどが登録されず毎回 サインアップページに飛ばされてしまう

最後にコールバック用のコントローラーとしてsns_callbacksが呼ばれるようにroutesファイルを編集

config/routes.rb

Rails.application.routes.draw do
  devise_for :users, controllers: { :omniauth_callbacks => "sns_callbacks" }
  get 'home/index'
  root 'home#index'

これでできるはず

github.com

参考サイト

qiita.com

qiita.com

thr3a.hatenablog.com