Programing

Rails CSRF Protection + Angular.js : protect_from_forgery는 POST에서 로그 아웃하도록합니다

lottogame 2020. 7. 5. 21:11
반응형

Rails CSRF Protection + Angular.js : protect_from_forgery는 POST에서 로그 아웃하도록합니다


protect_from_forgery옵션이 application_controller에 언급되어 있으면 로그인하고 GET 요청을 수행 할 수 있지만 처음 POST 요청에서 Rails는 세션을 재설정하여 로그 아웃합니다.

나는되어 protect_from_forgery일시적으로 해제 옵션을하지만, Angular.js와 함께 사용하고 싶습니다. 그렇게 할 방법이 있습니까?


DOM에서 CSRF 값을 읽는 것이 좋은 해결책은 아니라고 생각합니다.

다음은 angularJS 공식 웹 사이트 http://docs.angularjs.org/api/ng.$http 의 문서 양식입니다 .

도메인에서 실행되는 JavaScript만이 쿠키를 읽을 수 있으므로 서버는 XHR이 도메인에서 실행되는 JavaScript에서 온 것임을 확신 할 수 있습니다.

이를 활용하려면 (CSRF Protection) 서버가 첫 번째 HTTP GET 요청에서 XSRF-TOKEN이라는 JavaScript 읽기 가능 세션 쿠키에 토큰을 설정해야합니다. 후속 비 GET 요청에서 서버는 쿠키가 X-XSRF-TOKEN HTTP 헤더와 일치하는지 확인할 수 있습니다.

다음은 해당 지침을 기반으로 한 솔루션입니다.

먼저 쿠키를 설정하십시오.

# 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

기본 Rails CSRF 보호 ( <%= csrf_meta_tags %>)를 사용하는 경우 다음 과 같이 Angular 모듈을 구성 할 수 있습니다.

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

또는 CoffeeScript를 사용하지 않는 경우 (what !?) :

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
]

Also, be sure to check out HungYuHei's answer, which covers all the bases on the server rather than the client.


The angular_rails_csrf gem automatically adds support for the pattern described in HungYuHei's answer to all your controllers:

# Gemfile
gem 'angular_rails_csrf'

The answer that merges all previous answers and it relies that you are using Devise authentication gem.

First of all, add the gem:

gem 'angular_rails_csrf'

Next, add rescue_from block into 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

And the finally, add the interceptor module to you angular app.

# 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')

I saw the other answers and thought they were great and well thought out. I got my rails app working though with what I thought was a simpler solution so I thought I'd share. My rails app came with this defaulted in it,

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

I read the comments and it seemed like that is what I want to use angular and avoid the csrf error. I changed it to this,

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

And now it works! I don't see any reason why this shouldn't work, but I'd love to hear some insight from other posters.


I've used the content from HungYuHei's answer in my application. I found that I was dealing with a few additional issues however, some because of my use of Devise for authentication, and some because of the default that I got with my application:

protect_from_forgery with: :exception

I note the related stack overflow question and the answers there, and I wrote a much more verbose blog post that summarises the various considerations. The portions of that solution that are relevant here are, in the application controller:

  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

I found a very quick hack to this. All I had to do is the following:

a. In my view, I initialize a $scope variable which contains the token, let's say before the form, or even better at controller initialization:

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

b. In my AngularJS controller, before saving my new entry, I add the token to the hash:

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

Nothing more needs to be done.


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

It's working on angularjs side!

참고URL : https://stackoverflow.com/questions/14734243/rails-csrf-protection-angular-js-protect-from-forgery-makes-me-to-log-out-on

반응형