Используя Rails 3.0.6, Omniauth 0.2.0 и Devise 1.2.1, я столкнулся со следующей ситуацией:
Я хочу предложить пользователям возможность аутентификации через Facebook. У меня есть пользовательская система, настроенная с помощью Devise, и я могу успешно авторизоваться с помощью Facebook. Я потратил несколько часов, пытаясь закодировать поведение, которое я хочу для одной конкретной ситуации:
- пользователь не авторизован
- у пользователя есть аккаунт на сайте
- пользователь аутентифицируется через Facebook
Я предлагаю пользователю 2 варианта на данный момент
- создать учетную запись (может быть фиктивной учетной записью без предоставленной информации)
- связать эту аутентификацию Facebook с существующей учетной записью
У меня проблемы с последним вариантом. Пользователь уже прошел аутентификацию, но мне все еще нужно, чтобы он вошел в систему со своей учетной записью на сайте. У меня есть действие в моем AuthenticationsController
, которое свяжет эту аутентификацию с вошедшим в систему пользователем. Devise, похоже, не предлагает мне способа войти в систему, оставаясь в одном и том же действии. Это была моя первая попытка сделать это
class AuthenticationsController < ApplicationController
before_filter :authenticate_user!, :only => :auth_link_existing_user
...
def auth_link_existing_user
...
end
Однако при использовании этого метода, если пользователь входит в систему, он просто перенаправляется на корневую страницу моего сайта. Я знаю, что могу изменить перенаправление входа Devise, но это будет для всех входов. Я хотел, чтобы только эта ситуация имела отдельный редирект.
После прочтения этот вопрос списка рассылки, я попытался расширить SessionsController
своим собственным поведением:
def create
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
if params[:redirect] #new
redirect_to params[:redirect].to_sym #new
else
respond_with resource, :location => redirect_location(resource_name, resource)
end
end
Это тоже не работает. Я определил свой маршрут auth_link_existing_user
для использования глагола POST (что кажется точным), и перенаправления могут быть только GET.
Итак, теперь у меня есть решение: скопировать и вставить код из помощника Devise authenticate_user!
в новую функцию, которую можно вызывать внутри действия контроллера без перенаправления. Мне это кажется далеко не идеальным, потому что это дублирует код и увеличивает связанность — обновление Devise или Warden, которое меняет это поведение, также сломает мой код.
Кто-нибудь еще пробовал что-то подобное и придумал более элегантное решение? Вы видите более простой способ предложить такое или подобное поведение своим пользователям?
ОБНОВЛЕНИЕ: Для тех, кто хочет использовать мое грязное решение в конце, вот что я сделал:
def auth_link_existing_user
# FROM Devise/sessions/create
resource = warden.authenticate!(:scope => :user, :recall => "registrations#auth_new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(:user, resource)
# method defined in Ryan Bates' Railscast for Omniauth w/Devise
current_user.apply_omniauth(session[:omniauth])
current_user.save
end
обратите внимание, что это действие ДОЛЖНО быть размещено в вашем контроллере сеансов. В противном случае Warden выдаст вам сообщение об ошибке «неверный адрес электронной почты/пароль». Это был невероятно долгий процесс отладки, чтобы найти источник.
Имея это на месте, я использую форму входа для отправки этого действия после аутентификации пользователя.