認証機能(Devise)

ログインやユーザ管理の機能を持つDeviseと、Twitter認証の機能を持つOmniAuth Twitterを利用して、Twitter認証の機能を実装します。

実装の流れは以下のようになります。

1.DeviseとOmniAuth Twitterの導入 2.Userモデルの作成 3.認証処理の実装 4.Twitter認証の設定

Deviseの導入

DeviseとOmniAuth Twitterのgemをインストールします。

Gemfileをエディタで開き、この行の後に

gem 'carrierwave'

この行を追加します。

gem 'devise'
gem 'omniauth-twitter'

gemをインストールします。RAILS_ROOTでbundle installを実行します。

bundle install

Deviseのファイルを生成します。

rails g devise:install

実行するとこのようなメッセージが表示されます。

% 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

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

メッセージにしたがって、必要な部分の対応を行います。

今回は利用しませんが、Deviseの機能を使うとユーザ作成や認証時にメールを送信できます。この機能を利用するための設定を追加します。

config/environments/development.rbをエディタで開きます。この行の後に

# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false

この行を追加します。

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

root_url(開発環境の場合は http://localhost:3000 )にアクセスした時に表示されるページを指定します。今回は /photos にリダイレクトさせます。

config/routes.rbをエディタで開きます。この行のあとに

Rails.application.routes.draw do

この行を追加します。

  root to: redirect('/photos')

認証した時のメッセージを表示できるようにします。

app/views/layouts/application.html.erbをエディタで開きます。この行のあとに

<div class="container">

この行を追加します。

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

メッセージが重複して表示されてしまうため、不要になったコードを削除します。

app/views/photos/index.html.erb をエディタで開きます。この行を削除します。

<p id="notice"><%= notice %></p>

4と5の対応はいまは必要ないのでなにもしません。

Userモデルの作成

認証で利用するUserモデルを自動生成します。

rails g devise User

コードが自動生成されます。

rails g devise User
      invoke  active_record
      create    db/migrate/20151216140212_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users

マイグレーションファイルはこのような内容になっています。Deviseにはアカウントロックなどの機能があり、それらに必要なカラムが定義されています。

db/migrate/20151216140212_devise_create_users.rb

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

Userモデルはこのようなコードになっています。Deviseで利用できるモジュールが定義されています。

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

Twitter認証で利用するカラムを追加します。

カラム 説明
provider 認証で使うサービス。OmniAuthTwitterで利用します
uid ユーザーのID。OmniAuthTwitterで利用します
user_name ユーザ名
avatar_url アイコン画像のURL

マイグレーションファイルを自動生成します。

rails g migration add_columns_to_users provider uid user_name avatar_url

マイグレーションファイルが自動生成されました。

rails g migration add_columns_to_users provider uid user_name avatar_url
      invoke  active_record
      create    db/migrate/20151216235406_add_columns_to_users.rb

マイグレーションファイルはこのような内容になっています。

db/migrate/20151216235406_add_columns_to_users.rb

class AddColumnsToUsers < ActiveRecord::Migration
  def change
    add_column :users, :provider, :string
    add_column :users, :uid, :string
    add_column :users, :user_name, :string
    add_column :users, :avatar_url, :string
  end
end

マイグレーションを実行します。

rake db:migrate

このような実行結果が表示されます。

rake db:migrate
== 20151217223032 AddColumnsToUsers: migrating ================================
-- add_column(:users, :provider, :string)
   -> 0.0007s
-- add_column(:users, :uid, :string)
   -> 0.0003s
-- add_column(:users, :user_name, :string)
   -> 0.0004s
-- add_column(:users, :avatar_url, :string)
   -> 0.0003s
== 20151217223032 AddColumnsToUsers: migrated (0.0019s) =======================

Twitter認証の設定

https://apps.twitter.com/ で、作成したアプリケーションの情報を表示します。「Keys and Access Tokens」のタブをクリックすると以下の情報が表示されます。

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

config/secrets.ymlにTwitter認証で利用するKeyを追加します。development: の前に設定を追加します。

default_twitter: &default_twitter
  twitter_api_key: (API Keyの値)
  twitter_api_secret: (API Secretの値)

development: と test: の設定のあとにTwitter認証の情報を追加します。

development:
  secret_key_base: 9cbxxxxx617b21163a7d31f1280e6973a62ea0a21a98e233173dd29ccde7809ea3eef72d9e220216b3e2fea1a82b7013c632a89f0acf4b8f77713e7d9528fc8b
  <<: *default_twitter

test:
  secret_key_base: 4fbxxxxx4de9c47525a3365728fca18fa5e4401aeff049b8c7a3a624e828ebc2aa94d53693a5a01f348d1abd33e363e4fbb3da84742702be431c39e569b1881b
  <<: *default_twitter

本番環境では環境変数からtwitter認証の情報を取得するようにします。この行のあとに

production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

この行を追加します。

  twitter_api_key: <%= ENV["TWITTER_API_KEY"] %>
  twitter_api_secret: <%= ENV["TWITTER_API_SECRET"] %>

config/initializers/devise.rbで、Twitter認証で利用するKeyを設定します。

最後のendの前に3行追加します。

  config.omniauth :twitter,
    Rails.application.secrets.twitter_api_key,
    Rails.application.secrets.twitter_api_secret

app/models/user.rb に OmniAuth を利用する設定を追加します。この行の最後に

devise :database_authenticatable, :registerable,
       :recoverable, :rememberable, :trackable, :validatable

:omniauthable を追加します。

devise :database_authenticatable, :registerable,
       :recoverable, :rememberable, :trackable, :validatable, :omniauthable

認証処理の実装

Twitter認証は以下のような流れで行います。

  1. ログインリンクをクリックする
  2. Twitterの認証画面にリダイレクトする
  3. Twitterと連携する
  4. Twitterからのコールバックでアプリケーションにリダイレクトされる
  5. Twitterからアクセストークンを使ってユーザーの認証 or ユーザーの登録を行う

2-4はDeviseの設定を行うことで実現できるため、実装する必要はありません。1と5の実装を行います。

コールバック用コントローラーの実装

最初にTwitterからのコールバックを受け取るコントローラーを自動生成します。

rails g controller users/omniauth_callbacks

このようなファイルが自動生成されます。

rails g controller users/omniauth_callbacks
      create  app/controllers/users/omniauth_callbacks_controller.rb
      invoke  erb
      create    app/views/users/omniauth_callbacks
      invoke  test_unit
      create    test/controllers/users/omniauth_callbacks_controller_test.rb
      invoke  helper
      create    app/helpers/users/omniauth_callbacks_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/users/omniauth_callbacks.coffee
      invoke    scss
      create      app/assets/stylesheets/users/omniauth_callbacks.scss

コントローラーにコールバックを受け取るアクションを実装します。

app/controllers/users/omniauth_callbacks_controller.rb をエディタで開きます。編集前はこのような内容になっています。

class Users::OmniauthCallbacksController < ApplicationController
end

このように書き換えます。

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def twitter
    callback_from :twitter
  end

  private

  def callback_from(provider)
    provider = provider.to_s

    @user = User.find_or_create_from_oauth(request.env['omniauth.auth'])

    if @user.persisted?
      flash[:notice] = I18n.t('devise.omniauth_callbacks.success', kind: provider.capitalize)
      session[:user_id] = @user.id
      sign_in_and_redirect @user, event: :authentication
    else
      session["devise.#{provider}_data"] = request.env['omniauth.auth']
      redirect_to new_user_registration_url
    end
  end

end

ApplicationControllerではなくDevise::OmniauthCallbacksControllerを継承するようにします。

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

プロバイダー名(サービス)に対応したアクションが必要なため、twitterというアクションを実装しています。

def twitter
  callback_from :twitter
end

実際の処理はcallback_fromメソッドが担当します。

ユーザーがすでに存在する場合はDBから取得し、存在しない場合は新規作成します。このメソッドはあとでmodelに実装します。request.env['omniauth.auth']にはTwitterからのアクセストークンが格納されています。

@user = User.find_or_create_from_oauth(request.env['omniauth.auth'])

@user.persisted?でモデルが保存済みか確認します。

if @user.persisted?

正しく保存されている場合は、表示するメッセージを設定し、Deviseの機能を使ってリダイレクトします。

ユーザー情報の検索と保存

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

  def self.find_or_create_from_oauth(auth)
    User.find_or_create_by(provider: auth.provider, uid: auth.uid) do |user|
      user.user_name   = auth.info.nickname
      user.avatar_url = auth.info.image
      user.email      = User.dummy_email(auth)
      user.password   = Devise.friendly_token[0, 20]
    end
  end

  private

  def self.dummy_email(auth)
    "#{auth.uid}-#{auth.provider}@example.com"
  end

end

ユーザーがすでに存在する場合はDBから取得し、存在しない場合は新規作成します。 find_or_create_byメソッドはActiveRecordの機能です。ユーザーが存在しなかった場合は、Twitterからのアクセストークンの情報を使ってユーザーを新規作成しています。

def self.find_or_create_from_oauth(auth)
  User.find_or_create_by(provider: auth.provider, uid: auth.uid) do |user|
    user.user_name   = auth.info.nickname
    user.avatar_url = auth.info.image
    user.email      = User.dummy_email(auth)
    user.password   = Devise.friendly_token[0, 20]
  end
end

emailは今回利用ないため、ダミーデータを登録しています。ダミーデータを作成するメソッドを実装します。

def self.dummy_email(auth)
  "#{auth.uid}-#{auth.provider}@example.com"
end

Deviseが利用するURLを定義します。

config/routes.rbをエディターで開ます。この行を

devise_for :users

このように書き換えます。

devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }

ログイン、ログアウトを行うためのリンクを追加します。

app/views/layouts/application.html.erbをエディタで開きます。この行のあとに

<div class="container">

これらのコードを追加します。

<% if user_signed_in? %>
  <%= link_to 'logout', destroy_user_session_path, method: :delete %>
<% else %>
  <%= link_to 'Twitter login', user_omniauth_authorize_path(:twitter) %>
<% end %>

user_signed_in?はDeviseの機能で、ログインしているかを確認できます。

<% if user_signed_in? %>

サーバーを起動し、ログインできるか確認します。

rails s

ログイン後にユーザーが作成されているか確認します。サーバーを起動したターミナルとは別のターミナルで、Railsコンソールを起動します。

rails c

コンソールで最後のユーザを取得します。

User.last

このような情報が保存されていました。

[1] pry(main)> User.last
  User Load (0.5ms)  SELECT  "users".* FROM "users"  ORDER BY "users"."id" DESC LIMIT 1
#<User:0x007fe6020122b0> {
                        :id => 1,
                     :email => "1111111-twitter@example.com",
        :encrypted_password => "xxxxx",
      :reset_password_token => nil,
    :reset_password_sent_at => nil,
       :remember_created_at => nil,
             :sign_in_count => 2,
        :current_sign_in_at => Thu, 17 Dec 2015 22:53:46 UTC +00:00,
           :last_sign_in_at => Thu, 17 Dec 2015 22:53:09 UTC +00:00,
        :current_sign_in_ip => "::1",
           :last_sign_in_ip => "::1",
                :created_at => Thu, 17 Dec 2015 22:53:09 UTC +00:00,
                :updated_at => Thu, 17 Dec 2015 22:53:46 UTC +00:00,
                  :provider => "twitter",
                       :uid => "1111111",
                 :user_name => "sada_h",
                :avatar_url => "http://pbs.twimg.com/profile_images/660349716178292737/y16rKnHm_normal.jpg"

課題

  • ログイン後にユーザー名とユーザーのアイコンが表示出来るようにしましょう。
  • Facebook認証を実装しましょう
  • photoとuserを紐付けましょう
  • ログインしていないとphotoの新規登録/編集/削除ができないようにしましょう。