Рельсы - InvalidAuthenticityToken для запросов json/xml

По какой-то причине я получаю InvalidAuthenticityToken при отправке запросов в мое приложение при использовании json или xml. Насколько я понимаю, rails должен требовать токен подлинности только для запросов html или js, и поэтому я не должен сталкиваться с этой ошибкой. Единственное решение, которое я нашел до сих пор, это отключение protect_from_forgery для любого действия, к которому я хотел бы получить доступ через API, но это не идеально по очевидным причинам. Мысли?

    def create
    respond_to do |format|
        format.html
        format.json{
            render :json => Object.create(:user => @current_user, :foo => params[:foo], :bar => params[:bar])
        }
        format.xml{
            render :xml => Object.create(:user => @current_user, :foo => params[:foo], :bar => params[:bar])
        }
    end
end

и это то, что я получаю в журналах всякий раз, когда я передаю запрос к действию:

 Processing FooController#create to json (for 127.0.0.1 at 2009-08-07 11:52:33) [POST]
 Parameters: {"foo"=>"1", "api_key"=>"44a895ca30e95a3206f961fcd56011d364dff78e", "bar"=>"202"}

ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
  thin (1.2.2) lib/thin/connection.rb:76:in `pre_process'
  thin (1.2.2) lib/thin/connection.rb:74:in `catch'
  thin (1.2.2) lib/thin/connection.rb:74:in `pre_process'
  thin (1.2.2) lib/thin/connection.rb:57:in `process'
  thin (1.2.2) lib/thin/connection.rb:42:in `receive_data'
  eventmachine (0.12.8) lib/eventmachine.rb:242:in `run_machine'
  eventmachine (0.12.8) lib/eventmachine.rb:242:in `run'
  thin (1.2.2) lib/thin/backends/base.rb:57:in `start'
  thin (1.2.2) lib/thin/server.rb:156:in `start'
  thin (1.2.2) lib/thin/controllers/controller.rb:80:in `start'
  thin (1.2.2) lib/thin/runner.rb:174:in `send'
  thin (1.2.2) lib/thin/runner.rb:174:in `run_command'
  thin (1.2.2) lib/thin/runner.rb:140:in `run!'
  thin (1.2.2) bin/thin:6
  /opt/local/bin/thin:19:in `load'
  /opt/local/bin/thin:19

7 ответов

С protect_from_forgery При включенном Rails требуется токен аутентификации для любых не-GET запросов. Rails автоматически включает токен аутентификации в формы, созданные с помощью помощников форм, или ссылки, созданные с помощью помощников AJAX, поэтому в обычных случаях вам не придется об этом думать.

Если вы не используете встроенную форму Rails или помощников AJAX (возможно, вы используете ненавязчивый JS или каркас JS MVC), вам придется самостоятельно установить токен на стороне клиента и отправить его вместе с вашим данные при отправке запроса POST. Вы бы поместили такую ​​строку в <head> вашего макета:

<%= javascript_tag "window._token = '#{form_authenticity_token}'" %>

Тогда ваша AJAX-функция отправит токен с другими вашими данными (пример с jQuery):

$.post(url, {
    id: theId,
    authenticity_token: window._token
});

Это то же самое, что и ответ @user1756254, но в Rails 5 вам нужно использовать немного другой синтаксис:

protect_from_forgery unless: -> { request.format.json? }

Источник: http://api.rubyonrails.org/v5.0/classes/ActionController/RequestForgeryProtection.html

У меня была похожая ситуация, и проблема была в том, что я не отправлял через правильные заголовки типов контента - я запрашивал text/json и я должен был просить application/json,

я использовал curl следующее для проверки моего приложения (при необходимости измените):

curl -H "Content-Type: application/json" -d '{"person": {"last_name": "Lambie","first_name": "Matthew"}}' -X POST http://localhost:3000/people.json -i

Или вы можете сохранить JSON в локальный файл и вызвать curl как это:

curl -H "Content-Type: application/json" -v -d @person.json -X POST http://localhost:3000/people.json -i

Когда я изменил заголовки типа контента на право application/json все мои проблемы исчезли, и мне больше не нужно было отключать защиту от подделки.

Другой способ - избегать verify_authenticity_token, используя skip_before_filter в вашем приложении Rails:

skip_before_action :verify_authenticity_token, only: [:action1, :action2]

Это позволит curl делать свою работу.

Добавляя ответ на andymism, вы можете использовать его, чтобы применить включение TOKEN по умолчанию в каждый запрос POST:

$(document).ajaxSend(function(event, request, settings) {
    if ( settings.type == 'POST' ||  settings.type == 'post') {
        settings.data = (settings.data ? settings.data + "&" : "")
            + "authenticity_token=" + encodeURIComponent( window._token );
    }
});

Пока JavaScript находится на веб-сайте, обслуживаемом Rails (например, фрагмент JS или приложение React, управляемое через webpacker), вы можете использовать значение в csrf_meta_tagsвключены по умолчанию.

В application.html.erb:

      <html>
  <head>
    ...
    <%= csrf_meta_tags %>
    ...

Поэтому в HTML вашего сайта:

      <html>
  <head>
    <meta name="csrf-token" content="XZY">

Возьмите жетон из contentсвойство и использовать его в запросе:

      const token = document.head.querySelector('meta[name="csrf-token"]').content

const response = await fetch("/entities/1", {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ authenticity_token: token, entity: { name: "new name" } })
});

Это похоже на ответ @andrewle, но в дополнительном токене нет необходимости.

Чтобы добавить к ответу Фернандо, если ваш контроллер отвечает и на json, и на html, вы можете использовать:

      skip_before_filter :verify_authenticity_token, if: :json_request?
Другие вопросы по тегам