LoginSignup
8
9

More than 3 years have passed since last update.

【Rails】SNS認証(Twitter、Facebook、google)

Posted at
  • ユーザー登録において、メールアドレス認証の他に、Twitter、Facebook、googleなどのSNS認証機能を付けたい。
  • ライブラリ(Gem)のOmniAuthを使用( SNSのAPIにリクエストを送ると、API上で認証後、SNSの登録情報がアプリ側に返される流れ)。
  • 参: OmniAuth のGithub
  • 参: google-oauth2 のGit Hub

実装する機能

  1. メールアドレスでのユーザー登録。
  2. SNS認証でのユーザー登録(名前とメールアドレスを自動入力、PW不要。その他の情報は入力させる)。
  3. メールアドレス登録済みなら、SNSを紐付ける

手順

1. API側の設定 〜 アプリ側の基本設定

2. アプリ側の実装(メソッドの設定)

2-0. (事前準備)メールアドレスでの登録機能の実装

  • 追加したカラムを受け取るためのストロングパラメータ設定。
application_controller.rb
before_action :configure_permitted_parameters, if: :devise_controller?

private
def configure_permitted_parameters  # メールアドレス以外の自分で追加したカラムを許可
  devise_parameter_sanitizer.permit(:sign_up, keys: [:name, ..])
end
  • 仮の確認用のビューを作成.
users/index.html.haml
- if user_signed_in?
  = "#{current_user.name}でログイン中"
  = link_to 'ログアウト', destroy_user_session_path, method: :delete
- else
  = link_to '新規登録', new_user_path
  = link_to 'ログイン', new_user_session_path
users/new.html.haml
= link_to 'メールアドレスで登録', new_user_registration_path

ここで、動作確認しとく。

2-1. SNS認証(OmniAuth)の実装

  • 今回は、SNS認証と、Userモデルに保存するタイミングが異なる!
    • SNS情報から、取得したユーザー情報を照合(今回は、クラスメソッドで切り出して実装)。
    • SNS照合結果から、アプリ登録済みユーザーか? → ログイン or 新規登録画面へ遷移。
  • 公式ドキュメントには、「登録ボタンクリック → ユーザー登録完了」の仕様が記載。

コントローラー

  • twitter、facebook、google_oauth2アクションから処理(コールバック関数名:authorization)を呼ぶ。
    • SNS認証時に呼ばれるコールバック関数名は決まってる。
  • APIからのレスポンスがrequest.env["omniauth.auth"]変数に入る。
  • DB操作を行うメソッドUser.from_omniauthを作り、変数を渡す。
    • このメソッドで、Userモデルのインスタンスを返してもらって、@userに代入しとくことで、deviseのヘルパーが使えるようになる。
  • @userが未保存(新規登録)の場合、ビューに@user変数を渡したいので、renderを使用。
users/omniauth_callbacks_controller.rb
  def twitter
    authorization   # コールバック
  end

  def facebook
    authorization   # コールバック
  end

  def google_oauth2
    authorization   # コールバック
  end

  def failure
    redirect_to root_path
  end

  private
  # コールバックのメソッド定義
  def authorization   # APIから取得したユーザー情報はrequest.env["omniauth.auth"]に格納されてる
    sns_info = User.from_omniauth(request.env["omniauth.auth"])     # User.from_omniauth は、モデルで定義(次項)
    @user = sns_info[:user]    # deviseのヘルパーを使うため、@user に代入(ハッシュ(モデルの返り値)から値を取得)

    if @user.persisted? # ユーザー登録済み(ログイン処理)
      sign_in_and_redirect @user, event: :authentication   # authenticationのcallbackメソッドを呼んで、@user でログイン
      set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
    else                # ユーザー未登録(新規登録画面へ遷移)
      @sns_id = sns_info[:sns].id                  # ハッシュ(モデルの返り値)から取得した、認証データを一時的に保持(ユーザー登録ページに渡す)
      render template: new_user_registration_url   # ユーザー登録ページに遷移
    end
  end
参)request.env["omniauth.auth"] に格納されてる情報
  • provider (必須) : プロバイダー(例:twitter、facebookなど)
  • uid(必須) : プロバイダーに固有の識別子(文字列)。
  • info (必須) : ユーザー情報(ハッシュ)
    • name(必須) : 表示名(通常、姓名の連結。指定子、ニックネームの場合も)。
    • email : メールアドレス(一部サイトでは提供なし)
    • nickname : ユーザー名(Twitterの@名、アカウント名など)
    • first_name
    • last_name
    • location : ユーザーのロケーション(通常、市と州)。
    • description : ユーザー説明文。
    • image : プロフィール画像(URL、50x50ピクセルの正方形)。
    • phone : 電話番号
    • urls : サイト/URL識別子のキー/値のペアを含むハッシュ(例: "Blog" => "http://intridea.com/blog")
  • credentials : アクセストークンや資格情報を提供する場合、これを通過する。
    • token : OAuth、OAuth 2.0プロバイダーが提供するアクセストークン。
    • secret : OAuthプロバイダーが提供するアクセストークンシークレット。
    • expires : アクセストークンに有効期限があるかを示すブール値
    • expires_at : 有効期限(FacebookとGoogle Plusは返す。Twitter、LinkedInにはない)。
  • extra : 追加情報(プロバイダー固有の形式)。
    • raw_info : 全情報のハッシュ(例:Twitterの場合、APIから返されたJSONハッシュ)。

モデル

  • User.from_omniauth メソッドの中身を作成(ユーザー登録で渡すデータの定義)。
  • sns認証したことがあれば、アソシエーションで取得し、ログイン。なければ、SNS認証時のnameとemail情報を取得。
SNS認証のコールバック時に呼ばれるメソッドを定義(user.rb)
def self.from_omniauth(auth)       # snsから取得した、providerとuidを使って、既存ユーザーを検索
  sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create  # sns認証したことがあればアソシエーションで取得、なければSns_credentialsテーブルにレコード作成

  # snsのuser or usersテーブルに対し、SNS認証で取得したメールアドレスが登録済みの場合は、取得 or なければビルド(保存はしない)
  user = sns.user || User.where(email: auth.info.email).first_or_initialize(
    name: auth.info.name,
    email: auth.info.email
  )
  if user.persisted?   # userが登録済みの場合:そのままログインするため、snsのuser_idを更新しとく
    sns.user = user
    sns.save
  end
  { user: user, sns: sns }   # user、snsをハッシュで返す(コントローラーがこれを受け取る)
end
  • omniauth_callbackコントローラー → APIにリクエストを送る。コントローラーでの処理はないので、passthruアクション不要。
    • new_record? : 未保存なら、trueを返す
    • persisted? : 未保存、かつ、削除履歴ナシなら、trueを返す

ビュー

  • SNS認証用のリンクを設置。
  • このリンクは、SNS認証での新規登録/ログインを兼ねてる。
例)自分で設置する場合(users/new.html.haml、devise/sessions/new.html.haml)
= link_to 'Twitterで登録', user_twitter_omniauth_authorize_path, method: :post
= link_to 'Facebookで登録', user_facebook_omniauth_authorize_path, method: :post
= link_to 'Googleで登録', user_google_oauth2_omniauth_authorize_path, method: :post
例)共通化する場合(devise/shared/_links.html.haml)
- if devise_mapping.omniauthable?
  .omniauth_btn
    - resource_class.omniauth_providers.each do |provider|
      = link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider)
  • 最初のSNS認証時は、user_idがない(user_idが確定してない)。なので、sns保存には、モデルに、belongs_to user, optional: trueオプション(外部キーのnil許可)を忘れずに!
  • 2回目以降のログイン時/SNS認証時は、登録済みのため、user_idカラムが更新される。

SNS認証時、PW不要にする

  • SNS認証時だけ、PWを自動生成するイメージ。
  • 定義した変数を使って、PW入力欄の表示/非表示を分ける。
devise/registrations/new.html.haml
- if @sns_id.present?  # @sns_idがあれば、非表示(hidden_fieldで、params[:sns_auth]としてtrueを返す)
  = hidden_field_tag :sns_auth, true
- else   # @sns_id がなければPW入力欄を表示
  .field
    = f.label :password
    %em (#{@minimum_password_length} 文字以上)
    %br/
    = f.password_field :password, autocomplete: "new-password"
  .field
    = f.label :password_confirmation
    %br/
    = f.password_field :password_confirmation, autocomplete: "new-password"
  • paramsは通常、devise/registrationsのcreateアクションに送られるが、今回は、コントローラーをオーバーライドする。users/registrations_controller.rbを作成( createアクションの動作を変更(SNS認証時のみPW不要)したいため )。
    • オーバーライド : 親クラスで定義したメソッドと同じ名前のメソッドを、子クラスでも定義した場合、子クラスで定義したメソッドの方が実行されること。
users/registrations_controller.rb
def create
  if params[:sns_auth] == 'true'
    pass = Devise.friendly_token    # PWの自動生成
    params[:user][:password] = pass
    params[:user][:password_confirmation] = pass
  end
  super
end
  • rubyでは、継承先クラスでsuperメソッドを使うと、親モデルの同名メソッドを実行できる(devise/registrationsのcreateアクションを呼んでくれる)。
    • params[:sns_auth]が送られた時だけ、Devise.friendly_tokenでPWを自動生成し、それ以外の場合はsuperメソッドで、deviseに処理を任せる。
8
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
9