web-dev-qa-db-ja.com

フェニックス-無効なCSRF(クロスサイト偽造保護)トークンエラー

レコードを更新(または作成)しようとすると、無効なCSRFトークンエラーが発生します。 Elixir v1.0.3、Erlang/OTP 17 [erts-6.3]、Phoenix v0.8.0を使用しています(Phoenixのバージョンを確認する方法がわかりません)。私は主にPhoenixガイドとElixirDose JobsiteExampleリソースに従ってWebアプリを作成しています。ただし、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's 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がどこから来たのか正確にはわからないので、それを参照する方法がわかりません(または、これを完全に間違って行っている可能性があります)。

どんなアイデアでも大歓迎です。

21
Paul B

インストールされているバージョンを確認するには、

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

これは、depsディレクトリにあるフェニックスを示しています。

また、フェニックス0.9.0にアップグレードすると、状況が変更された場合(Plug.CSRFProtectionの更新により)、CSRFはセッションの代わりにCookieを使用して異なる動作をします。

から v0.9.0(2015-02-12)のPhoenix変更ログ

[プラグ] Plug.CSRFProtectionはセッションの代わりにCookieを使用し、「csrf_token」の代わりに「_csrf_token」パラメーターを期待するようになりました

トークンの値にアクセスするには、サーバー側で次のように見えるCookieからifを取得します。

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が必要だったので、簡単にアクセスできるように、JQueryCookieを使用してJavaScript内のCookieにアクセスする方法を次に示します。次のコマンドを実行すると、ブラウザに値が表示されるはずです。

$.cookie("_csrf_token")

これは次のようなものを返す可能性があります

"K9UDa23e1sacdadfmvu zzOD9VBHTSr1c/lcvWY="

上記のスペースは、フェニックスでは+にURLエンコードされていたため、CSRFが失敗していました。プラグのバグ、または単に処理するものであるかどうかはわかりません。そのため、今のところ、+を明示的に処理しています。

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

CSRFトークンにアクセスできるので、x-csrf-tokenをリクエストヘッダーに追加する必要があります( ありがとう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へのリンクを適切に投稿するのに十分なレピュテーションポイントがありませんでしたが、グーグルで検索するのは簡単なはずです。

4
a4word

フェニックスのバージョン0.13では、次のことができます。

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

ファイルにあるのでweb/web.exこの関数のインポートがあります。

21
Tsuharesu

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という名前の入力タグが自動的に含まれます。"

8
Tayler

http://phoenix.thefirehoseproject.com で答えを見つけました。 csrfトークンを取得する関数を作成する必要があります。

web/view.ex

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

次に、テンプレートでそれを取得します。

web/template/directory/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) %>">

以上です!

2
Paul B

私の解決策は次のとおりです。

  • _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() %>" />

1
Viet Tran

私の場合、それは行plug :scrub_params問題の原因。行にコメントした後、それは機能しました。ただし、scrub_paramsがないとアプリは安全ではないため、必ず修正する必要があります。

0
Pratik Khadloya