web-dev-qa-db-ja.com

Rails CSRF Protection + Angular.js:protect_from_forgeryを使用すると、POSTからログアウトできます

Application_controllerにprotect_from_forgeryオプションが記載されている場合、ログインしてGETリクエストを実行できますが、最初にPOSTリクエストRailsがセッションをリセットし、それによりログが記録されますでる。

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

126
Paul

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
275
HungYuHei

デフォルトの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の答え も確認してください。これは、クライアントではなくサーバー上のすべてのベースをカバーします。

78
Michelle Tilley

angular_Rails_csrf gemは、 HungYuHei's answer で説明されているパターンのサポートをすべてのコントローラーに自動的に追加します。

# Gemfile
gem 'angular_Rails_csrf'
29
jsanders

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

まず、gemを追加します。

gem 'angular_Rails_csrf'

次に、rescue_fromブロックをapplication_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

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

# 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')
4
Anton Orel

私は他の答えを見て、それらは素晴らしいと考え抜かれたと思いました。私は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

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

1
Blaine Hatab

これに非常に簡単なハックを見つけました。私がしなければならなかったのは次のことです:

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 = {}

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

1
Ruby Racer

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
PaulL
 angular
  .module('corsInterceptor', ['ngCookies'])
  .factory(
    'corsInterceptor',
    function ($cookies) {
      return {
        request: function(config) {
          config.headers["X-XSRF-TOKEN"] = $cookies.get('XSRF-TOKEN');
          return config;
        }
      };
    }
  );

これは、angularjs側で機能しています!

0