Подтвердить что ты не робот

Phoenix - ошибка токена CSRF (Cross Site Forgery Protection)

При попытке обновить (или создать) запись я получаю недопустимую ошибку CSRF-токена. Я использую Elixir v1.0.3, Erlang/OTP 17 [erts-6.3] и Phoenix v0.8.0 (я думаю, я не уверен, как проверить версию Phoenix). Я создаю веб-приложение, в основном следуя инструкциям Phoenix и ресурсам примера Elixir Dose Jobsite. Однако, когда я пытаюсь опубликовать информацию из html-формы, я получаю ошибку токена CSRF. Следуя рекомендациям, приведенным в ошибке, я добавил "x-csrf-token": csrf_token к действию.

edit.html.eex:

<h2>Edit Directory</h2>
<form class="form-horizontal" action="<%= directory_path @conn, :update, @directory.id, 'x-csrf-token': @csrf_token %>" method="post">
  <div class="form-group">
    <label for="directory" class="col-sm-2 control-label">Directory</label>
    <div class="col-sm-10">
      <input type="hidden" name="_method" value="PATCH">
      <input type="text" class="form-control" value="<%= @directory.directory %>" name="directory" placeholder="Directory" required="required">
    </div>
  </div>
...

но я получаю следующую ошибку:

[error] #PID<0.579.0> running Ainur.Endpoint terminated
Server: localhost:4000 (http)
Request: POST /config/directories/2?x-csrf-token=
** (exit) an exception was raised:
    ** (Plug.CSRFProtection.InvalidCSRFTokenError) Invalid CSRF (Cross Site Forgery Protection) token. Make sure that all your non-HEAD and non-GET requests include the csrf_token as part of form params or as a value in your request headers with the key 'x-csrf-token'
        (plug) lib/plug/csrf_protection.ex:54: Plug.CSRFProtection.call/2
        (ainur) web/router.ex:4: Ainur.Router.browser/2
        (ainur) lib/phoenix/router.ex:2: Ainur.Router.call/2
        (plug) lib/plug/debugger.ex:104: Plug.Debugger.wrap/3
        (phoenix) lib/phoenix/endpoint/error_handler.ex:43: Phoenix.Endpoint.ErrorHandler.wrap/3
        (ainur) lib/ainur/endpoint.ex:1: Ainur.Endpoint.phoenix_endpoint_pipeline/2
        (plug) lib/plug/debugger.ex:104: Plug.Debugger.wrap/3
        (phoenix) lib/phoenix/endpoint/error_handler.ex:43: Phoenix.Endpoint.ErrorHandler.wrap/3

Насколько я могу судить (будучи новым для Elixir, Phoenix и HTML), "действие" - это по существу путь, и любые параметры, которые я размещаю в нем, вернутся к приложению. И, действительно, я обнаружил, что x-csrf-token = "" передается обратно маршрутизатору, поэтому @csrf_token не может быть правильным. Я точно не знаю, откуда происходит csrf_token, поэтому я не знаю, как его ссылать (или, возможно, я делаю это совершенно неправильно).

Будем очень благодарны за любые идеи.

4b9b3361

Ответ 1

Чтобы увидеть установленную версию, запустите

cat ./deps/phoenix/mix.exs | grep version

Что показывает вам, какой феникс у вас есть в папке deps.

Кроме того, если/при обновлении до phoenix 0.9.0, все изменилось (из-за обновлений для Plug.CSRFProtection), CSRF работает по-разному, используя файлы cookie вместо сеансов.

Из Список изменений Phoenix для v0.9.0 (2015-02-12)

[Plug] Plug.CSRFProtection теперь использует cookie вместо сеанса и ожидает параметр "_csrf_token" вместо "csrf_token"

Чтобы получить доступ к значению маркера, возьмите, если из файла cookie, который на стороне сервера выглядит как

Map.get(@conn.req_cookies, "_csrf_token")

Итак, для вашего кода будет выглядеть примерно как

<h2>Edit Directory</h2>
<form class="form-horizontal" action="<%= directory_path @conn, :update, @directory.id, 'x-csrf-token': Map.get(@conn.req_cookies, "_csrf_token") %>" method="post">
  <div class="form-group">
    <label for="directory" class="col-sm-2 control-label">Directory</label>
    <div class="col-sm-10">
      <input type="hidden" name="_method" value="PATCH">
      <input type="text" class="form-control" value="<%= @directory.directory %>" name="directory" placeholder="Directory" required="required">
    </div>
  </div>

Теперь для полноты я нуждался в обновленном CSRF для запросов, построенных с чисто клиентской стороны, поэтому я получил доступ к куки файлу в javascript, используя куки JQuery, для удобства доступа. Вы должны увидеть это значение в своем браузере, выполнив следующие

$.cookie("_csrf_token")

Что может вернуть что-то вроде

"K9UDa23e1sacdadfmvu zzOD9VBHTSr1c/lcvWY="

Обратите внимание на вышесказанное, пространство, которое в phoenix кодировалось url до +, что все еще приводило к сбою CSRF. Теперь это ошибка в Plug, или просто что-то, что нужно обработать, я не уверен, поэтому на данный момент я просто обрабатываю + явно

$.cookie("_csrf_token").replace(/\s/g, '+');

С доступом к токену CSRF нам просто нужно добавить x-csrf-токен в ваш заголовок запроса (спасибо ilake). Вот код, чтобы заставить его работать с работой ajax-вызова (соответственно, заполнить URL-адрес и данные и ответ).

$.ajax({ 
  url: 'YOUR URL HERE',
  type: 'POST',
  beforeSend: function(xhr) {
    xhr.setRequestHeader('x-csrf-token', $.cookie("_csrf_token").replace(/\s/g, '+'))
  },
  data: 'someData=' + someData,
  success: function(response) {
    $('#someDiv').html(response);
  }
});

Обратите внимание, что вы также можете отправить параметр _csrf_token в качестве параметра, но я предпочитаю выше, и он чувствует себя более чистым для меня.

Заключительное примечание. У меня не было достаточного количества точек репутации для правильной публикации ссылки на jquery cookie, но это должно быть легко для Google.

Ответ 2

В версии 0.13 Phoenix вы можете сделать

<input type="hidden" name="_csrf_token" value="<%= get_csrf_token() %>">

потому что в файле web/web.ex есть импорт этой функции.

Ответ 3

В качестве другого решения, доступного с v0.10.0, вы можете позволить Phoenix вводить вход CSRF для вас.

Пример из руководства по обновлению:

<%= form_tag("/hello", method: :post) %>
... your form stuff. input with csrf value is created for you.
</form>

Это выведет тег формы и несколько входных тегов, включая _csrf_token. Результаты будут выглядеть примерно так:

<form accept-charset="UTF-8" action="/hello" method="post">
    <input name="_csrf_token" value="[automatically-inserted token]" type="hidden">
    <input name="_utf8" value="✓" type="hidden">
</form>

form_tag docs: "для запросов" post ", тег формы будет автоматически включать тег ввода с именем _csrf_token."

Ответ 4

Я нашел ответ на http://phoenix.thefirehoseproject.com. Вы должны создать функцию для получения токена csrf:

веб/view.ex

def csrf_token(conn) do
  Plug.Conn.get_session(conn, :csrf_token)
end

Затем верните его в шаблон:

веб/шаблон/Каталог/edit.html.eex

<form class="form-horizontal" action="<%= directory_path @conn, :update, @directory.id %>" method="post">
   <input type="hidden" name="csrf_token" value="<%= csrf_token(@conn) %>">

И что это!

Ответ 5

В моем случае это была строка plug :scrub_params, вызывающая проблему. После комментирования строки это сработало. Но нужно убедиться, что это исправить, поскольку приложение будет небезопасно без scrub_params.

Ответ 6

Мое решение:

  • Импортировать Phoenix.Controller.get_csrf_token:0 в MyModule.view:0 (в папке apps/my_app_web/lib/my_app_web.ex):

    import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]

  • Добавить скрытый параметр в моей форме:

    <input type="hidden" name="_csrf_token" value="<%= get_csrf_token() %>"/>