Rails4.2.1 + Devise + Twitter 認証 (Bootstrapも入れる）


Railsで新規アプリ作ってなんかサービス作ろうと思ったら、大体ログイン機能は作るだろうし、とりあえずBootstrap入れておきたいよねということで、Twitter IDでログイン出来るアプリのひな形を作る手順をメモしておきます。

DeviseとOmniAuthを利用します。

```ruby:Gemfile
gem 'devise'
gem 'omniauth-twitter'
```

```shell
% bundle install
% rails g devise:install
```

devise:installで色々指示が出てくる。
1はとりあえずメール飛ばしたりしないのでやらない。
2は言うとおりやる。
3は、後述のBootstrap Generatorでapplication.htmlを生成するのでやらない。
4はRails4なのでやらない。
5もtwitter認証でのログインしか作らないのでやらない。

```
      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

===============================================================================
```

home#indexを生成する

```shell
% rails g controller home index
```

モデルを生成して、認証に利用するカラムを追加する。

```shell
% rails g devise User
% rails g migration AddOmniauthToUser uid:string provider:string name:string
```

migrateファイルいじって、indexつける

```ruby
class AddOmniauthToUser < ActiveRecord::Migration
  def change
    add_column :users, :uid, :string
    add_column :users, :provider, :string
    add_column :users, :name, :string

    add_index :users, [:uid, :provider], unique: true
  end
end
```

```shell
% rake db:create
% rake db:migrate
```

deviseで使うモジュールの選択

```ruby:user.rb
  devise :database_authenticatable, :trackable, :omniauthable
```

OmniAuth用のrouting作成

```ruby:routes.rb
  devise_for :users, controllers: {
                       omniauth_callbacks: "users/omniauth_callbacks"
                   }
```

```shell
% mkdir app/controllers/users
% touch app/controllers/users/omniauth_callbacks_controller.rb
```

```ruby:omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < ApplicationController

end
```

twitterのアプリ作成して、API KEYなど取得しておく。

deviseのomniauth設定


```ruby:initializers/devise.rb

  config.omniauth :twitter, ENV['APP_ID'], ENV['APP_SECRET']

```

キーをコードに入れたくないので、環境変数から取るようにした。

開発環境の環境変数はdotenv-railsで管理する。

```ruby:Gemfile
group :development do
  gem 'dotenv-rails'
end
```

```.env
APP_ID=HOGEHOGE
APP_SECRET=HOGEHOGEfu48rueufif
```

コード管理から外す

```.gitignore
.env
```

Userモデルに、omniauth時に呼ばれるメソッドを作る
Userを返す。（無ければ作る）メソッド作成。
emailとパスワードはとりあえず使わないので、ユニークになる適当な値を突っ込む。

```ruby:user.rb
  def self.find_for_twitter_oauth(auth)
    user = User.where(:provider => auth.provider, :uid => auth.uid).first
    unless user
      user = User.create(name:     auth.info.nickname,
                         provider: auth.provider,
                         uid:      auth.uid,
                         email:    User.create_unique_email,
                         password: Devise.friendly_token[0,20]
      )
    end
    user
  end

  def self.create_unique_string
    SecureRandom.uuid
  end

  def self.create_unique_email
    User.create_unique_string + "@example.com"
  end
```

omniauthのcallback時の処理。Userを見つけるか、作るかして sign_inする。

```ruby:omniauth_callbacks_controller.rb
  def twitter
    @user = User.find_for_twitter_oauth(request.env["omniauth.auth"])
    if @user.persisted?
      flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Twitter"
      sign_in_and_redirect @user, :event => :authentication
    else
      redirect_to root_path
    end
  end
```

sign_inするためのリンクをViewに作る。
下記の余談にて、Bootstrap railsを当てている前提で、一番上のナビゲーションバーに sign_in, sign_outのリンクを作る。
sign_in時は twitter名を出して sign_in 出来ているのを分かるようにする。

9行目くらいからが追加した部分です。これで画面の右上に、Sign in with Twitter とか出ればOK。

```haml:application.html.haml
        #navbar.collapse.navbar-collapse
          %ul.nav.navbar-nav
            %li.active
              %a{href: "#"} Home
            %li
              %a{href: "#about"} About
            %li
              %a{href: "#contact"} Contact
          %ul.nav.navbar-nav.navbar-right
            - if user_signed_in?
              %li
                %a{href: "#"}
                  = current_user.name
              %li
                = link_to "Sign out", destroy_user_session_path, :method => :delete
            - else
              %li
                = link_to "Sign in with Twitter", user_omniauth_authorize_path(:twitter)

```

Sign inしていないとアクセス出来ないようにするには、コントローラに以下のように書けばいいです。

```ruby:home_controller.rb
class HomeController < ApplicationController
  before_action :authenticate_user!
end
```

これで、URL直接叩いてアクセスしようとすると、rootのpathへリダイレクトされます。

## Bootstrap railsにあてる(余談)
Rails assetsを使ってbootstrap-sassを入れているので bundlerのバージョンを1.8.4以降にする必要があります。

```ruby:Gemfile
gem 'bundler', '>= 1.8.4'

source 'https://rails-assets.org' do
  gem 'rails-assets-bootstrap-sass'
end
gem 'bootstrap-generators'

gem 'haml-rails'
```

```shell
% rails g bootstrap:install -e haml
% rm app/views/layouts/application.html.erb
```