Rails 3 и Devise: аутентификация пользователя при выполнении действия POST

Используя 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 выдаст вам сообщение об ошибке «неверный адрес электронной почты/пароль». Это был невероятно долгий процесс отладки, чтобы найти источник.

Имея это на месте, я использую форму входа для отправки этого действия после аутентификации пользователя.


person Eric Hu    schedule 12.04.2011    source источник


Ответы (1)


Мне нравится чистота вашего решения, хотя оно уходит глубже в стек.

Вот как я реализовал нечто подобное, следуя примеру Devise+Omniauth Facebook на Разработать вики и изменить метод facebook для передачи информации о сеансе в форму входа примерно так:

if @user.persisted?
    flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
    sign_in_and_redirect @user, :event => :authentication

else
    session["devise.facebook_data"] = request.env["omniauth.auth"]
    redirect_to new_user_session_url
end

Затем, в вашем случае, я бы проверил действие контроллера входа в систему для session["devise.facebook_data"], отправил uid + токен с формой и apply_omniauth, если он присутствует.

person T C    schedule 09.06.2012