Каков наилучший способ аутентификации пользователей с помощью Devise 3 и Backbone?

Я работаю с этим стеком:

  • Core API RESTful с Rails 4 и Dev 3.2.
  • Другое приложение/позиция с Backbone

Я прочитал много статей, руководств, разделов stackoverflow, случайных результатов Google, блогов и т.д., но все они очень устарели.

Используя практический подход (tl; dr здесь), мне просто нужно получить реальный сеанс между Devise 3 и Backbone на разных серверах и удерживать его, как два отдельных проекта. Удаленный вход, вы знаете.

Я действительно застрял в этом, поэтому я бы очень признателен за ваши предложения.

Спасибо, ребята.

Ответ 1

Лично у меня такая же ситуация в моем проекте с Angular, а не с Backbone как интерфейсом и Rails 4 API с Devise. Я постараюсь судить о вас в предположении, что я правильно понял ваш вопрос.

Чтобы правильно работать с сеансами в вашем сценарии, вы должны быть уверены, что:

  • Браузеры правильно обрабатывают связь (т.е. не взаимодействуют с вашими данными, потому что запросы не соответствуют политике CORS).
  • и ваши запросы проходят через защиту Rails CSRF.

Пожалуйста, прочитайте эту статью о CORS. Если вы не знакомы с CORS, статья должна предоставить необходимую информацию для моего ответа. Некоторая информация о защите CSRF здесь

Ниже приведен ваш шаг за шагом:

  • Backbone.js отправляет запрос GET, например http://yourserver/signin
  • Сервер Rails отправляет куки файлы сеанса, которые будут храниться в маркерах браузера и CSRF, которые могут быть сохранены где-то в вашем приложении Backbone.
  • Backbone.js отправляет POST запрос с учетными данными пользователя (имя, пароль) и токен CSRF в заголовках и текущий неавторизованный сеанс в файлах cookie. Очень важно, чтобы запрос содержал информацию о сеансе. В противном случае ему будет предоставлен другой токен CSRF на стороне Rails, и вы получите сообщение WARNING: Can't verify CSRF token authenticity.
  • Backbone.js возвращает авторизованный сеанс, если учетные данные верны.

Вот что можно сделать, чтобы заставить его работать:

  • Брандмауэр Rails должен корректно реагировать на запросы от front-end. Это означает, что он должен:

    • Отвечайте на OPTIONS запросы (предполетные запросы)
    • Отправьте правильные заголовки CORS
    • Возможность передавать токен CSRF с интерфейсом
  • Передняя часть должна:

    • Возможность отправлять запросы с учетными данными
    • Получить и использовать правильный токен CSRF

Самый простой способ научить ваш Rails-сервер реагировать на запросы CORS - использовать rack-cors. Это также даст правильные заголовки CORS.

config.middleware.insert_before Warden::Manager, Rack::Cors do
  allow do
    origins '*' # it highly recommended to specify the correct origin
    resource '*', 
        :headers => :any, 
        :methods => [:get, :post, :options], # 'options' is really important 
                                            # for preflight requests
        :expose  => ['X-CSRF-Token']   #allows usage of token on the front-end
  end
end

Последняя вещь на стороне сервера - это предоставить токен CSRF. Пользовательский разработчик должен отлично справиться с этой задачей.

class SessionsController < Devise::SessionsController

    after_action :set_csrf_header, only: [:new, :create, :destroy]

    #...

    protected

    def set_csrf_header
      response.headers['X-CSRF-Token'] = form_authenticity_token
    end
end

Обратите внимание, что вам нужен токен CSRF при отправке первого запроса GET (new) при отправке учетных данных через POST запрос (create) и при выходе из приложения, отправив DELETE запрос (destroy). Если вы не отправляете токен CSRF при выводе, вы не сможете выполнить вход без перезагрузки страницы.

И где-то в config/routes.rb не забудьте указать, что вы используете пользовательский контроллер:

/config/routes.rb
  devise_for :users, :controllers => {:sessions => "sessions"}

Теперь, к интерфейсу. Пожалуйста, посмотрите этот script, который переопределяет стандартный Backbone.sync и обрабатывает связь с сервером Rails. Это почти хорошо с парой исправлений:

  beforeSend: function( xhr ) {
    if (!options.noCSRF) {
      // we dont have csrf-token in the document anymore  
      //var token = $('meta[name="csrf-token"]').attr('content');

      // New Line #1
      // we will get CSRF token from your application.
      // See below for how it gets there.
      var token = YourAppName.csrfToken;

      if (token) xhr.setRequestHeader('X-CSRF-Token', token);  

      // New Line #2
      // this will include session information in the requests
      xhr.withCredentials = true;
    }

  //..some code omitted
  //................

  // Trigger the sync end event
  var complete = options.complete;
  params.complete = function(jqXHR, textStatus) {
     // New Lines #3,4
     // If response includes CSRF token we need to remember it
     var token = jqXHR.getResponseHeader('X-CSRF-Token') 
     if (token) YourAppName.csrfToken = token;

     model.trigger('sync:end');
     if (complete) complete(jqXHR, textStatus);
  };
 }

Я не уверен, что это квалифицируется как полный ответ на ваш вопрос, но, по крайней мере, с этого можно начать. Это может быть не самый лучший способ, но так оно и есть. Дайте знать, если у вас появятся вопросы.