1. 公式ドキュメント
1.1. heartcombo/devise
2. エラー
2.1. Gyazo
2.1.1. user_id doesn't have a default value
2.1.1.1. 解消方法
2.1.1.1.1. Google Facebook SNS認証エラー|teratail
3. SNS認証の際はパスワードの入力を不要にする機能の実装
3.1. SNS認証の際はパスワードを自動生成する
3.1.1. User.from_omniauthメソッドの修正
3.1.1.1. models/user.rb # 変更前 user # 変更後 { user: user, sns: sns }
3.1.2. コントローラーの修正
3.1.2.1. controllers/users/omniauth_callbacks_controller.rb # 変更前 @user = User.from_omniauth(request.env["omniauth.auth"]) if @user.persisted? sign_in_and_redirect @user, event: :authentication #this will throw if @user is not activated else render template: 'devise/registrations/new' end # 変更後 sns_info = User.from_omniauth(request.env["omniauth.auth"]) @user = sns_info[:user] if @user.persisted? sign_in_and_redirect @user, event: :authentication #this will throw if @user is not activated else @sns_id = sns_info[:sns].id render template: 'devise/registrations/new' end
3.1.2.1.1. - @userへ代入する際にハッシュから値を取り出す - @sns_idという変数を作り、新規登録画面で扱えるようにする
3.1.3. ビューでパスワードの表示/非表示をできるよう修正
3.1.3.1. views/devise/registrations/new.html.haml - if @sns_id.present? = hidden_field_tag :sns_auth, true - else .field = f.label :password - if @minimum_password_length %em (#{@minimum_password_length} characters minimum) %br/ = f.password_field :password, autocomplete: "new-password" .field = f.label :password_confirmation %br/ = f.password_field :password_confirmation, autocomplete: "new-password"
4. ロジックの構築:SNSメソッドを実装するためのメソッドの実装
4.1. 1. Facebook(またはGoogle)で認証のボタンがクリックされる 2. WebAPI側にリクエストが送られる 3. 認証を経て、omniauth_callbacksコントローラにSNSに登録されている情報が返される 4. SNSの情報から、ユーザー情報のみを取得して、既存のユーザー情報と照合を行う(今回の場合はこの処理をクラスメソッドとして切り出す) 5. その照合結果から、今回SNSで認証されたユーザーが、すでにアプリケーションに登録されているユーザーなのか判断する 6. 照合の結果、既存のユーザーが存在しない場合は、新規登録画面に遷移する 7. すでに同じ情報のユーザーがアプリケーションのDBに存在している場合は、ログイン処理を行う
4.2. 基本的な動きをコントローラーに定義
4.2.1. controllers/users/omniauth_callbacks_controller.rb def facebook authorization end def google_oauth2 authorization end def failure redirect_to root_path end private def authorization @user = User.from_omniauth(request.env["omniauth.auth"]) if @user.persisted? #ユーザー情報が登録済みなので、新規登録ではなくログイン処理を行う sign_in_and_redirect @user, event: :authentication else #ユーザー情報が未登録なので、新規登録画面へ遷移する render template: 'devise/registrations/new' end end
4.2.1.1. ポイント
4.2.1.1.1. ・SNS認証時に呼ばれるコールバック関数名が決まっているのでfacebook、google_oauth2というアクションから目的の処理authorizationを呼ぶ。 ・APIから受け取ったレスポンスがrequest.env["omniauth.auth"]という変数に入っている。 ・DB操作を行うメソッドUser.from_omniauthを仮で作り、上記変数を渡す。 ・このメソッドにはUserモデルのインスタンスを返させて@userへ代入することで、その後の処理でdeviseのヘルパーを利用することができる。 ・@userが保存済みで無い場合(新規登録する場合)、@userという変数をそのまま新規登録のviewsで利用するためにrenderを使用する。
4.3. User.from_omniauthの中身を定義
4.3.1. user.rb def self.from_omniauth(auth) sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create # sns認証したことがあればアソシエーションで取得 # 無ければemailでユーザー検索して取得orビルド(保存はしない) user = sns.user || User.where(email: auth.info.email).first_or_initialize( nickname: auth.info.name, email: auth.info.email ) # userが登録済みの場合はそのままログインの処理へ行くので、ここでsnsのuser_idを更新しておく if user.persisted? sns.user = user sns.save end user end
4.4. ビューへSNS認証でのリンクを設置
4.4.1. views/users/new.html.haml // 以下を追記 = link_to 'Facebookで登録', user_facebook_omniauth_authorize_path, method: :post = link_to 'Googleで登録', user_google_oauth2_omniauth_authorize_path, method: :post
4.4.1.1. API側にリクエストを出すためのリンクだよ
4.4.1.2. このリンクはSNS認証での新規登録とログインを兼ねているので表示する文字だけ変えてviews/devise/sessions/new.html.hamlに記述すればログインボタンが完成
4.4.1.2.1. views/devise/sessions/new.html.haml = link_to 'Facebookでログイン', user_facebook_omniauth_authorize_path, method: :post = link_to 'Googleでログイン', user_google_oauth2_omniauth_authorize_path, method: :post
5. 準備:SNS認証に必要なモデル、コントローラーを作成
5.1. SNS認証を経てデータベースに反映するためのモデルを作成
5.1.1. $ rails g model sns_credential provider:string uid:string user:references $ rails db:migrate
5.2. モデルでomnioathを使うためのオプションとアソシエーションを追加
5.2.1. models/user.rb class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:facebook, :google_oauth2] has_many :sns_credentials
5.2.2. models/sns_credential.rb # associations belongs_to :user, optional: true
5.3. deviseのコントローラーをカスタマイズするためのコントローラーを作成
5.3.1. $ rails g devise:controllers users
5.4. 作成したコントローラをdeviseが使用するようにルーティングを変更
5.4.1. config/routes.rb # 変更前 devise_for :users # 変更後 devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks', registrations: 'users/registrations' }
5.5. 環境変数が読み込めているか確認
5.5.1. rails c
5.5.1.1. $ rails c irb(main):001:0> ENV['FACEBOOK_CLIENT_ID'] => "1111111111111111" # 数字だけ irb(main):002:0> ENV['FACEBOOK_CLIENT_SECRET'] => "1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1" # 数字と文字列 irb(main):003:0> ENV['GOOGLE_CLIENT_ID'] => "~~~~~~~googleusercontent.com" # 末尾がこの形 irb(main):004:0> ENV['GOOGLE_CLIENT_SECRET'] => "1a1a1a1a1aa1_1a1a1a1_1a" # 英数字と_(アンダーバー)が含まれる
6. FacebookとGoogleで使用するAPIキーを環境変数に設定
6.1. $ vim ~/.bash_profile
6.1.1. export FACEBOOK_CLIENT_ID='メモしたアプリID' export FACEBOOK_CLIENT_SECRET='メモしたapp secret' export GOOGLE_CLIENT_ID='メモしたクライアントID' export GOOGLE_CLIENT_SECRET='メモしたクライアントシークレット'
6.2. 変更後は$ source ~/.bash_profile
6.3. config/initializers/devise.rb config.omniauth :facebook,ENV['FACEBOOK_CLIENT_ID'],ENV['FACEBOOK_CLIENT_SECRET'] config.omniauth :google_oauth2,ENV['GOOGLE_CLIENT_ID'],ENV['GOOGLE_CLIENT_SECRET']
6.3.1. 環境変数をアプリ側に読みこむ記述を追加