Rails CSRF Protection + Angular.js:protect_from_forgeryにより、POSTでログアウトする


129

protect_from_forgeryオプションがapplication_controllerで言及されている場合、ログインしてGETリクエストを実行できますが、最初のPOSTリクエストでRailsがセッションをリセットし、ログアウトします。

このprotect_from_forgeryオプションを一時的にオフにしましたが、Angular.jsで使用したいと思います。それを行う方法はありますか?


これは、任意の、その程度の設定に役立ちますかどうかを確認してくださいHTTPヘッダstackoverflow.com/questions/14183025/...
マーク・Rajcok

回答:


276

DOMからCSRF値を読み取ることは良い解決策ではないと思います。これは単なる回避策です。

これは、angularJS公式ウェブサイトのドキュメント形式ですhttp://docs.angularjs.org/api/ng.$http

ドメインで実行されているJavaScriptのみがCookieを読み取ることができるため、サーバーでXHRがドメインで実行されているJavaScriptからのものであることが保証されます。

これ(CSRF保護)を利用するには、サーバーは最初のHTTP GETリクエストでXSRF-TOKENと呼ばれるJavaScript読み取り可能なセッションCookieにトークンを設定する必要があります。後続の非GET要求で、サーバーはCookieがX-XSRF-TOKEN HTTPヘッダーと一致することを確認できます

これらの指示に基づく私の解決策は次のとおりです。

まず、Cookieを設定します。

# app/controllers/application_controller.rb

# Turn on request forgery protection
protect_from_forgery

after_action :set_csrf_cookie

def set_csrf_cookie
  cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
end

次に、GET以外のすべてのリクエストでトークンを確認する必要があります。
Railsはすでに同じような方法で構築されているので、単純にオーバーライドしてロジックを追加できます。

# app/controllers/application_controller.rb

protected
  
  # In Rails 4.2 and above
  def verified_request?
    super || valid_authenticity_token?(session, request.headers['X-XSRF-TOKEN'])
  end

  # In Rails 4.1 and below
  def verified_request?
    super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
  end

18
クライアント側のコードを変更する必要がないため、この手法が気に入っています。
ミシェルティリー2013

11
このソリューションは、CSRF保護の有用性をどのように維持しますか?Cookieを設定することにより、マークされたユーザーのブラウザは、クロスサイトリクエストを含む後続のすべてのリクエストでそのCookieを送信します。悪意のあるリクエストを送信する悪意のあるサードパーティのサイトをセットアップすると、ユーザーのブラウザが「XSRF-TOKEN」をサーバーに送信します。このソリューションは、CSRF保護を完全にオフにすることと同じであるようです。
スティーブン

9
Angularドキュメントから:「ドメインで実行されているJavaScriptのみがCookieを読み取ることができるため、XHRがドメインで実行されているJavaScriptからのものであることをサーバーで確認できます。」@StevenXu-サードパーティのサイトはどのようにCookieを読み取るのですか?
ジミーベイカー

8
@JimmyBaker:はい、そうです。ドキュメントを確認しました。アプローチは概念的に健全です。私はCookieの設定と検証を混同しましたが、AngularフレームワークがCookieの値に基づいてカスタムヘッダーを設定していることに気付きませんでした!
スティーブン、

5
form_authenticity_tokenはRails 4.2の各呼び出しで新しい値を生成するため、これはもう機能していないようです。
Dave、

78

デフォルトのRails CSRF保護(<%= csrf_meta_tags %>)を使用している場合は、次のようにAngularモジュールを構成できます。

myAngularApp.config ["$httpProvider", ($httpProvider) ->
  $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
]

または、CoffeeScriptを使用していない場合(何ですか?):

myAngularApp.config([
  "$httpProvider", function($httpProvider) {
    $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content');
  }
]);

必要に応じて、次のようなGET以外のリクエストでのみヘッダーを送信できます。

myAngularApp.config ["$httpProvider", ($httpProvider) ->
  csrfToken = $('meta[name=csrf-token]').attr('content')
  $httpProvider.defaults.headers.post['X-CSRF-Token'] = csrfToken
  $httpProvider.defaults.headers.put['X-CSRF-Token'] = csrfToken
  $httpProvider.defaults.headers.patch['X-CSRF-Token'] = csrfToken
  $httpProvider.defaults.headers.delete['X-CSRF-Token'] = csrfToken
]

また、クライアントではなくサーバー上のすべてのベースをカバーするHungYuHeiの回答も必ず確認してください。


説明させてください。ベースドキュメントは.erbではなくプレーンHTMLであるため、使用できません<%= csrf_meta_tags %>。言及するprotect_from_forgeryだけで十分だと思いました。何をすべきか?ベースドキュメントはプレーンHTMLである必要があります(私はここでは選択しません)。
ポール、

3
あなたが使用する場合はprotect_from_forgery、あなたが言っている「私のJavaScriptコードは、Ajaxリクエストを行ったとき、私が送信することを約束されたX-CSRF-Token現在のCSRFトークンに対応することをヘッダー内に。」このトークンを取得するために、RailsはトークンをDOMに挿入し、<%= csrf_meta_token %>Ajaxリクエストを行うたびにjQueryでメタタグのコンテンツを取得します(デフォルトのRails 3 UJSドライバーがこれを行います)。ERBを使用していない場合、RailsからページやJavaScriptに現在のトークンを取得する方法がないためprotect_from_forgery、この方法では使用できません。
ミシェルティリー2013

説明ありがとうございます。従来のサーバー側アプリケーションではcsrf_meta_tags、サーバーが応答を生成するたび、およびこれらのタグが以前のものとは異なるたびにクライアント側が受信すると私が思ったこと。したがって、これらのタグはリクエストごとに一意です。問題は、AJAXリクエスト(角度なし)のアプリケーションがこれらのタグをどのように受信するかです。私はjQuery POSTリクエストでprotect_from_forgeryを使用しましたが、このCSRFトークンを取得することに煩わされることはありませんでした。どうやって?
ポール

1
RailsのUJSドライバの用途にjQuery.ajaxPrefilterここに示されているよう:github.com/indirect/jquery-rails/blob/c1eb6ae/vendor/assets/...は、このファイルを熟読し、すべてのRailsがしなくて、それはかなり動作させるために経由ジャンプフープ見ることができますそれについて心配してください。
ミシェルティリー2013

@BrandonTilleyは、これをオンputではpostなくオンにするだけで意味がないのではないでしょうcommonか?レールセキュリティガイドからThe solution to this is including a security token in non-GET requests
christianvuerings

29

angular_rails_csrf宝石が自動的に記述パターンのサポート追加HungYuHeiの答えを、すべてのコントローラに:

# Gemfile
gem 'angular_rails_csrf'

angular_rails_csrfを正しく使用するために、アプリケーションコントローラーとその他のcsrf / forgery関連の設定をどのように構成するべきか、という考えはありますか?
Ben Wheeler

このコメントの時点では、angular_rails_csrfgemはRails 5では機能しません。ただし、CSRFメタタグの値を使用してAngularリクエストヘッダーを構成すると機能します。
bideowego 2016

Railsの5をサポートしている宝石の新しいリリース、あり
jsanders

4

以前のすべての回答をマージし、Devise認証gem を使用していることに依存しています。

まず、gemを追加します。

gem 'angular_rails_csrf'

次に、rescue_fromapplication_controller.rb にブロックを追加します。

protect_from_forgery with: :exception

rescue_from ActionController::InvalidAuthenticityToken do |exception|
  cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
  render text: 'Invalid authenticity token', status: :unprocessable_entity
end

そして最後に、インターセプターモジュールを角度アプリに追加します。

# coffee script
app.factory 'csrfInterceptor', ['$q', '$injector', ($q, $injector) ->
  responseError: (rejection) ->
    if rejection.status == 422 && rejection.data == 'Invalid authenticity token'
        deferred = $q.defer()

        successCallback = (resp) ->
          deferred.resolve(resp)
        errorCallback = (resp) ->
          deferred.reject(resp)

        $http = $http || $injector.get('$http')
        $http(rejection.config).then(successCallback, errorCallback)
        return deferred.promise

    $q.reject(rejection)
]

app.config ($httpProvider) ->
  $httpProvider.interceptors.unshift('csrfInterceptor')

1
$injector直接注入するのではなく、なぜ注入するの$httpですか?
whitehat101 2014

これは機能しますが、追加したのはリクエストがすでに繰り返されているかどうかのチェックだけです。それが繰り返されたとき、それは永遠にループするので再度送信することはありません。
duleorlovic 2016

1

私は他の答えを見て、それらが素晴らしくてよく考えられていると思いました。Railsアプリはもっと​​簡単な解決策だと思っていたので動かしてみたので、共有したいと思いました。私のRailsアプリには、これがデフォルトで含まれていますが、

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception
end

私はコメントを読みましたが、それがangularを使用してcsrfエラーを回避したいようです。これに変更しました

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :null_session
end

そして今、それは機能します!これがうまくいかない理由はわかりませんが、他のポスターからいくつかの洞察を聞きたいです。


6
クライアント側からcsrf-tokenを送信していないため、偽造テストに失敗するとnilに設定されるため、rails 'sessions'を使用しようとすると問題が発生します。
hajpoj 14年

しかし、Railsセッションを使用していない場合は問題ありません。ありがとうございました!私はこれに対する最もきれいな解決策を見つけるのに苦労してきました。
モーガン

1

私のアプリケーションでは、HungYuHeiの回答のコンテンツを使用しました。私はいくつかの追加の問題に対処していることがわかりましたが、一部は認証にDeviseを使用したため、一部はアプリケーションで取得したデフォルトのためです。

protect_from_forgery with: :exception

私は関連するスタックオーバーフローの質問とその回答に注意し、さまざまな考慮事項をまとめたはるかに詳細なブログ投稿を書きました。ここに関連するそのソリューションの部分は、アプリケーションコントローラにあります。

  protect_from_forgery with: :exception

  after_filter :set_csrf_cookie_for_ng

  def set_csrf_cookie_for_ng
    cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
  end

  rescue_from ActionController::InvalidAuthenticityToken do |exception|
    cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
    render :error => 'Invalid authenticity token', {:status => :unprocessable_entity} 
  end

protected
  def verified_request?
    super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
  end

1

私はこれに対する非常に迅速なハックを見つけました。私がしなければならなかったすべては次です:

a。私の見解$scopeでは、トークンを含む変数を初期化します。たとえば、フォームの前、またはコントローラーの初期化でさらに良いとしましょう。

<div ng-controller="MyCtrl" ng-init="authenticity_token = '<%= form_authenticity_token %>'">

b。AngularJSコントローラーで、新しいエントリを保存する前に、ハッシュにトークンを追加します。

$scope.addEntry = ->
    $scope.newEntry.authenticity_token = $scope.authenticity_token 
    entry = Entry.save($scope.newEntry)
    $scope.entries.push(entry)
    $scope.newEntry = {}

これ以上何もする必要はありません。


0
 angular
  .module('corsInterceptor', ['ngCookies'])
  .factory(
    'corsInterceptor',
    function ($cookies) {
      return {
        request: function(config) {
          config.headers["X-XSRF-TOKEN"] = $cookies.get('XSRF-TOKEN');
          return config;
        }
      };
    }
  );

angularjs側で動作しています!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.