Rails 7 + Devise + omniauth-facebook + omniauth-twitterでログイン機能作成（基本から詳細まで）

この記事は devise、omniauth-facebook、omniauth-twitterのGemを使って、メールとFacebookとTwitterでログイン機能を作ります。

まずはFacebookとTwitter側の設定から始めます。

# Facebook側の設定

アプリを作成URL：[https://developers.facebook.com/apps/create](https://developers.facebook.com/apps/create)

![スクリーンショット 0003-11-07 10.58.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35605/a6ba4d02-6d33-f626-6d24-eac36edbc97e.png)

![スクリーンショット 0003-11-07 10.59.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35605/69070694-a7f6-66f3-3cce-ca221fa8ee2c.png)

新規アプリを作成してから「Facebookログイン」を設定します。

![スクリーンショット 0003-11-07 11.00.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35605/9b4a9bf1-7110-6b3a-c82a-66a4c52cc412.png)

Facebookログインの設定画面ではまず `public_profile`をAdvanced Accesssに切り替えて、それから「有効なOAuthリダイレクトURI」に　`<ホスト>/users/auth/facebook/callback` を入力します。

![スクリーンショット 0003-11-07 11.01.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35605/dd437f23-b610-15c3-d3c6-73440b92b087.png)

設定のベーシック画面でアプリのデータ使用状況の確認を完了を開始してから、`アプリID`と`アプリシークレット`をコピーしときます。

![っっs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35605/9ba19214-48dd-116d-6f1c-cf956e2ff5ab.png)

# Twitter側の設定

App detailsの設定の例は以下のようで、注意するところは二つ：
- `Enable Sign in with Twitter`をチェック
- `Callback URLs`に`<ホスト>/users/auth/twitter/callback`を入力

![名称未設定のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35605/f11aa156-9d7e-628c-b826-6a31d585c38c.png)

デフォルトはメール情報を取得できなくて、メール情報を取得するにはPermissionsタブで`Request email address from users`をチェックして保存する必要があります。

![スクリーンショット 0003-11-07 11.22.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35605/76a26a47-1c51-2333-cbe4-84a0c77570c4.png)

最後は Keys and tokensタブで `API key`と`Api secret key`をコピーしておきます。

![スクリーンショット 0003-11-07 11.23.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35605/361741ce-a965-4aad-ce0d-7ec3f0902278.png)

**ここまではFacebookとTwitterの設定が完了なので、次はソースコードに着手します！**

# Gem導入

```ruby:Gemfile
gem 'devise'
gem 'devise-i18n' # 英語で使うなら不要
gem 'omniauth-rails_csrf_protection'
gem 'omniauth-facebook'
gem 'omniauth-twitter'
```

# deviseのインストール

```
rails g devise:install
```

`User`というモデルでdeviseを作る

```
rails g devise User
```

カスタムのため、コントローラとビューと翻訳ファイルをコピーしておく

### deviseのコントローラ作成

```
rails g devise:controllers users
```

### deviseのビュー作成

```
rails g devise:views users
```

### deviseの翻訳ファイル作成

```
rails g devise:it8n:locale ja
```

**ビューのフォルダ名は`devise`ではなく`users`などであれば、deviseの翻訳ファイルを編集する必要があります**

```yaml:config/locales/devise.views.ja.yml
ja:
  # ・・・省略
  devise: &devise
  # ・・・省略
  users: *devise
```

# deviseの設定

```ruby:config/initializers/devise.rb
  config.omniauth :facebook,
                  ENV['FACEBOOK_APP_ID'],
                  ENV['FACEBOOK_APP_SECRET'],
                  {
                    scope: 'email',
                    image_size: 'large',
                    locale: 'en' # 英語で情報を取得
                  }
  config.omniauth :twitter,
                  ENV['TWITTER_CONSUMER_API_KEY'],
                  ENV['TWITTER_CONSUMER_API_SECRET_KEY'],
                  {
                    image_size: 'original',
                    lang: 'en' # 英語で情報を取得
                  }

  config.mailer_sender = 'LANGMEMO <notification@langmemo.com>' # deviseから送信するメールのアドレス

  config.scoped_views = true # カスタマイズのビューを使う場合
```

## アカウント登録、ログイン、ログアウトなどのURLヘルパー：

```slim:
= link_to 'アカウント登録', new_user_registration_path
= link_to 'ログイン', new_user_session_path
= button_to 'ログアウト', destroy_user_session_path, method: :delete, 'data-turbo': false
```

## アカウント登録時のカスタムフィールドを追加する場合

```ruby:config/routes.rb
  devise_for :users, controllers: {
    registrations: 'users/registrations'
  }
```

```ruby:app/controllers/users/registrations_controller.rb
    before_action :configure_sign_up_params, only: [:create]
    before_action :configure_account_update_params, only: [:update]

    protected

    # If you have extra params to permit, append them to the sanitizer.
    def configure_sign_up_params
      devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
    end

    # If you have extra params to permit, append them to the sanitizer.
    def configure_account_update_params
      devise_parameter_sanitizer.permit(:account_update, keys: [:name])
    end
```

## アカウント情報更新時に現在のパスワードを無くする場合

```ruby:app/controllers/users/registrations_controller.rb
    protected

    def update_resource(resource, params)
      resource.update_without_password(params)
    end
```

## アカウント登録して、メールで確認URLをクリックしてから自動ログインしたい場合

```ruby:config/routes.rb
  devise_for :users, controllers: {
    confirmations: 'users/confirmations'
  }
```

```ruby:app/controllers/users/confirmations_controller.rb
  protected

  # The path used after confirmation.
  def after_confirmation_path_for(resource_name, resource)
    # super(resource_name, resource)
    sign_in(resource)
    root_path
  end
```

## Deviseのフォームで送信した後のリダイレクトに`turbo`を`false`にしたい場合

`html: { 'data-turbo': false }` を入れる

```ruby:sessions/new.html.erb
  <%= form_for(resource, as: resource_name, url: session_path(resource_name), html: { 'data-turbo': false }) do |f| %>

```


**ここまではメールでアカウント登録、ログインができるようになりました。**


# FacebookとTwitterログイン

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

```ruby:app/models/user.rb
  devise :database_authenticatable, :registerable, :recoverable,
         :rememberable, :validatable, :confirmable, :trackable,
         :omniauthable # これを追加

  class << self
    def find_for_oauth(auth)
      @user = User.find_or_initialize_by(uid: auth.uid, provider: auth.provider)
      assign_info(auth)
      attach_image(auth.info.image) if auth.info.image
      @user.skip_confirmation!
      @user.save!
      @user
    rescue StandardError => e
      e.message
    end

    def assign_info(auth)
      @user.uid = auth.uid
      @user.provider = auth.provider
      @user.name = auth.info.name
      @user.email = auth.info.email
      @user.password = Devise.friendly_token[0, 20]
    end

    def attach_image(image_url)
      @user.image.attach(io: URI.parse(image_url).open, filename: "user_#{@user.id}_image")
      # ファイルの一番上に　require 'open-uri' が必要
    end
  end
```

```ruby:app/controllers/users/omniauth_callbacks_controller.rb
    def facebook
      callback_from :facebook
    end

    def twitter
      callback_from :twitter
    end

    private

    def callback_from(provider)
      @user = User.find_for_oauth(request.env['omniauth.auth'].delete_if { |k, _v| k == 'extra' })
      if @user.instance_of?(User)
        flash[:notice] = t('devise.omniauth_callbacks.success', kind: provider.to_s.capitalize)
        sign_in_and_redirect @user, event: :authentication
      else
        session["devise.#{provider}_data"] = request.env['omniauth.auth']
        redirect_to new_user_registration_path, alert: @user
      end
    end
```