現在完成間近の個人開発アプリにtwitterログインを導入したときにかなりハマったので備忘録として解決策をこちらに残しておきます。
エラー内容
twitter連携したときに、twitterアカウントで設定されている画像をActiveStorageでアタッチして、デフォルトのプロフィール画像に設定するという実装を行ったときに以下のようなエラーが起きました。(もちろん同時にtwitterのアカウント名も引っ張て来てます)
ActiveSupport::MessageVerifier::InvalidSignature
画像に関するコードをコメントアウトしたらエラーが消えたので、ActiveStorageが悪さをしているようでした。
画像のURLで直接アタッチしようとすると、不正な署名?としてActiveStorageから認識されてしまうようです。
解決策
流れとしては open-uriを使いtwitterの画像をダウンロードして、 ActiveStorageへ直接 IO インスタンスをアタッチするといったイメージです。(多少表現に誤謬がありましたらご指摘いただけると幸いです。)
以下にコードの一部を抜粋します。
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
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
最後まで読んでいただきありがとうございます!
日々学んだことをアウトプットしております。なにかご指摘などあればコメントいただけますと嬉しいです!!