【Rails】twitterログイン導入時にハマったActiveSupport::MessageVerifier::InvalidSignatureの解決方法【ActiveStorage】

現在完成間近の個人開発アプリにtwitterログインを導入したときにかなりハマったので備忘録として解決策をこちらに残しておきます。

#エラー内容
twitter連携したときに、twitterアカウントで設定されている画像をActiveStorageでアタッチして、デフォルトのプロフィール画像に設定するという実装を行ったときに以下のようなエラーが起きました。（もちろん同時にtwitterのアカウント名も引っ張て来てます）

```
ActiveSupport::MessageVerifier::InvalidSignature
```

画像に関するコードをコメントアウトしたらエラーが消えたので、ActiveStorageが悪さをしているようでした。

画像のURLで直接アタッチしようとすると、不正な署名？としてActiveStorageから認識されてしまうようです。

#解決策
流れとしては open-uriを使いtwitterの画像をダウンロードして、 ActiveStorageへ直接 IO インスタンスをアタッチするといったイメージです。（多少表現に誤謬がありましたらご指摘いただけると幸いです。）
以下にコードの一部を抜粋します。

```ruby:sessions_controller.rb

class SessionsController < ApplicationController
  before_action :forbid_login_user, only: %i[new]

  def new; end

  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
      user.activate!
      redirect_to user
    else
      user = User.find_by(email: params[:session][:email].downcase)
      if user&.authenticate(params[:session][:password])
        if user.activated?
          log_in user
          params[:session][:remember_me] == '1' ? remember(user) : forget(user)
          flash[:success] = 'ログインに成功しました。'
          redirect_to user
        else
          message  = 'アカウントが有効化されていません。 '
          message += 'Eメールのアカウント有効化リンクをクリックしてください。'
          flash[:warning] = message
          redirect_to root_url
        end
      else
        flash.now[:danger] = 'パスワードまたはEメールアドレスが間違っています。'
        render 'new'
      end
    end
  end
```

```ruby:user.rb
require 'open-uri'
# twitter認証
  def self.find_or_create_from_auth(auth)
    provider = auth[:provider]
    uid = auth[:uid]
    nickname = auth[:info][:nickname]
    email = User.dummy_email(auth)
    password = SecureRandom.urlsafe_base64
    # Twitterのオリジナルサイズのプロフィール画像パスを取得
    profile_image_url = auth.info.image.gsub("_normal","")

    self.find_or_create_by(provider: provider, uid: uid) do |user|
      user.nickname = nickname
      user.email = email
      user.password = password
      user.download_and_attach_avatar(profile_image_url)
    end
  end

  private

  # twitterAPIで取得した画像データをopen-uriでダウンロードし、IOインスタンスを直接アタッチする
  def download_and_attach_avatar(profile_image_url)
    return unless profile_image_url

    file = open(profile_image_url)
    avatar.attach(io: file,
                  filename: "profile_image.#{file.content_type_parse.first.split("/").last}",
                  content_type: file.content_type_parse.first)  
  end

  def self.dummy_email(auth)
    "#{auth.uid}-#{auth.provider}@example.com"
  end
```
ポイントは、```profile_image_url = auth.info.image.gsub("_normal","")```の部分です。
twitterクライアントから取得した画像URLには_normalという文字列が入っていて、画像が縮小されてしまっています。そのまま使うと画像が荒くなってしまうので、gsubメソッドを使い_nomalという文字列を削除しています。こうすることによってオリジナルの画像サイズのデータを取得することができます。

そして、```download_and_attach_avatar```メソッドに引数で```profile_image_url```を渡してダウンロードしてから、IOインスタンスとしてActiveStorageにアタッチしています。

このようにすることでエラーなく実装することができました。

探しても情報がほぼでてこず、試行錯誤してなんとかこの方法にたどり着きました。この記事がお役に立てれば幸いです。

ちなみに僕の場合、通常のログインでは、アカウントを有効化しないとログインできない仕様なので、user.activate!でactivatedカラムをfalseからtrueに強制的に変更してバリデーションを回避してます。（emailやpasswordでもバリデーションを回避するために```find_or_create_from_auth```メソッドでダミーデータをカラムに入れてます。）

#参考にした記事
https://rit-inc.hatenablog.com/entry/2018/04/02/160106
https://note.com/marikooota/n/n7ac0a66e34ea

#最後まで読んでいただきありがとうございます！
日々学んだことをアウトプットしております。なにかご指摘などあればコメントいただけますと嬉しいです！！