Help us understand the problem. What is going on with this article?

[Rails]SNSアカウントログイン機能の実装かつ既存userモデルと衝突しない方法

準備

※twitterとfacebookを例にする。基本的に同じパタンなので、googleなど他のSNSも簡単に適用できる。
※deviseは使わない
※私の既存user modelとsession controllerはRailsチュートリアルで書いたものと同じです

developerアカウント作成、自分のAPPを追加

Facebook

facebook developer (即時作成できる)
facebook_meitu_1.jpg
右上のメニューの中から追加したAPPを選択
Settings-BasicからAPP IDAPP SECRETを取得し、他のAPP情報も入力(ユーザ承認画面で表示される)
PRODUCTSのところでFacebook Loginを追加し、SettingsからValid OAuth Redirect URIs(https://domain_name/auth/facebook/callback) を入力
※セキュリティの関係で、facebookは今httpsのurlしか承認しないので、localhostは使えないです。実行してみたいならherokuにappをデプロイすることがおすすめです。※

Twitter

twitter developer (審査するには少し時間かかる)
twitter_meitu_2.jpg
右上のメニューの中から追加したAPPを選択
Keys and tokensからConsumer API keys (API key+API secret key)を取得
App detailsからCallback URL(http://localhost:3000/auth/twitter/callback 他複数追加可)を入力、他のAPP情報も入力(ユーザ承認画面で表示される)

gem追加

Gemfile
gem 'omniauth'
gem 'omniauth-twitter'
gem 'omniauth-facebook'

環境設定

./config/initializers/omniauth.rbというファイルを作成

omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV['TWITTER_ID'], ENV['TWITTER_SECRET']
  provider :facebook, ENV['FACEBOOK_ID'], ENV['FACEBOOK_SECRET']
end

追加可能なオプションのパラメータは
omniauth-facebook (github)
omniauth-twitter (github)
で確認できる。
例えばこんな感じ:

omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV['TWITTER_ID'], ENV['TWITTER_SECRET'],
    { :image_size => 'original', :authorize_params => { :lang => 'ja' } }
  provider :facebook, ENV['FACEBOOK_ID'], ENV['FACEBOOK_SECRET'],
    { display: 'page', image_size: 'normal' }
end

ENV['XXX']は環境変数です。XXXは自分で命名できます。

localhostで設定したい場合

(特にwindowsユーザはこの方法が一番便利かなと思う)
./config/local_env.ymlというファイルを作成 (facebookはlocalhost実行不可なので省略)

local_env.yml
TWITTER_ID: '自分のAPP_ID'
TWITTER_SECRET: '自分のAPP_SECRET'

./.gitignoreというファイルに下記のコードを追加(自分のkeyをgithubなどで公開しないように)

.gitignore
/config/local_env.yml

herokuで設定したい場合

$ heroku config:set TWITTER_ID=自分のAPP_ID
$ heroku config:set TWITTER_SECRET=自分のAPP_SECRET
$ heroku config:set FACEBOOK_ID=自分のAPP_ID
$ heroku config:set FACEBOOK_SECRET=自分のAPP_SECRET

各ファイルを更新

ルーティング

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

これが各developerアカウントのAPP設定で入力したredirect/callback URLになります

ユーザモデル

user.rb
#auth hashからユーザ情報を取得
#データベースにユーザが存在するならユーザ取得して情報更新する;存在しないなら新しいユーザを作成する
def self.find_or_create_from_auth(auth)
  provider = auth[:provider]
  uid = auth[:uid]
  name = auth[:info][:name]
  image = auth[:info][:image]
  #必要に応じて情報追加してください

  #ユーザはSNSで登録情報を変更するかもしれので、毎回データベースの情報も更新する
  self.find_or_create_by(provider: provider, uid: uid) do |user|
    user.username = name
    user.image_path = image
  end
end
$ rails g migration add_columns_to_users
20181122113757_add_columns_to_users.rb
class AddColumnsToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :uid, :string
    add_column :users, :provider, :string
  end
end
$ rails db:migrate

セッションコントローラ

sessions_controller.rb
def create
  auth = request.env['omniauth.auth']
  if auth.present?
    user = User.find_or_create_from_auth(request.env['omniauth.auth'])
    session[:user_id] = user.id
    redirect_back_or user
  else #既存パタン
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
      redirect_back_or user
    else
      flash.now[:danger] = 'メールアドレスとパスワードの組み合わせは有効ではありません'
      render 'new'
    end
  end
end

ユーザモデルのバリエーション

これだけじゃまだ動けない!
なぜなら、user modelでは色々なバリデーションがあるので、
self.find_or_create_by(provider: provider, uid: uid)という行でエラー発生。
※私のappではSNSアカウントでログインするならemailとpasswordがいらないので、バリデーションを以下のように修正した:

user.rb
validates :username, presence: true, unless: :uid? #他省略
validates :email, presence: true, unless: :uid?
has_secure_password validations: false
validates :password, presence: true, unless: :uid?

unless: :uid?というのは、
uidデータがあればusername, email, passwordデータなしでもUser.create()使えるようになります

ビュー

普通のテキストリンクでテストする場合

ログイン画面に追加

sessions/new.html.erb
<%= link_to "Twitterアカウントでログイン", "/auth/twitter" %>
<%= link_to "Facebookアカウントでログイン", "/auth/facebook" %>

統一のボタンの場合

sns_meitu_4.jpg

Gemfile
gem 'bootstrap-social-rails'
gem 'font-awesome-rails'
custom.scss
@import "bootstrap-social";
@import "font-awesome";
sessions/new.html.erb
<p>
  <%= link_to '/auth/twitter', class: "btn btn-social btn-twitter" do %>
    <span class="fa fa-twitter"></span> Twitterアカウントでログイン
  <% end %>
</p>
<p>
  <%= link_to '/auth/facebook', class: "btn btn-social btn-facebook" do %>
    <span class="fa fa-facebook"></span> Facebookアカウントでログイン
  <% end %>
</p>
<p>
  <%= link_to '/auth/google_auth2', class: "btn btn-social btn-google" do %>
    <span class="fa fa-google"></span> Googleアカウントでログイン
  <% end %>
</p>
julia817
まだまだ初心者ですが、よろしくお願いします。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした