request_forgery_protection.rb 5.3 KB
Newer Older
1
module ActionController #:nodoc:
2 3
  class InvalidAuthenticityToken < ActionControllerError #:nodoc:
  end
4 5 6 7

  module RequestForgeryProtection
    def self.included(base)
      base.class_eval do
8
        helper_method :form_authenticity_token
9
        helper_method :protect_against_forgery?
10 11 12 13
      end
      base.extend(ClassMethods)
    end
    
14
    # Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a
15
    # forged link from another site, is done by embedding a token based on a random string stored in the session (which an attacker wouldn't know) in all
16 17
    # forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller.  Only
    # HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication
P
Pratik Naik 已提交
18
    # scheme there anyway).  Also, GET requests are not protected as these should be idempotent anyway.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
    #
    # This is turned on with the <tt>protect_from_forgery</tt> method, which will check the token and raise an
    # ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the error message in
    # production by editing public/422.html.  A call to this method in ApplicationController is generated by default in post-Rails 2.0
    # applications.
    #
    # The token parameter is named <tt>authenticity_token</tt> by default. If you are generating an HTML form manually (without the
    # use of Rails' <tt>form_for</tt>, <tt>form_tag</tt> or other helpers), you have to include a hidden field named like that and
    # set its value to what is returned by <tt>form_authenticity_token</tt>. Same applies to manually constructed Ajax requests. To
    # make the token available through a global variable to scripts on a certain page, you could add something like this to a view:
    #
    #   <%= javascript_tag "window._token = '#{form_authenticity_token}'" %>
    #
    # Request forgery protection is disabled by default in test environment.  If you are upgrading from Rails 1.x, add this to
    # config/environments/test.rb:
    #
    #   # Disable request forgery protection in test environment
    #   config.action_controller.allow_forgery_protection = false
    # 
    # == Learn more about CSRF (Cross-Site Request Forgery) attacks
    #
    # Here are some resources:
    # * http://isc.sans.org/diary.html?storyid=1750
    # * http://en.wikipedia.org/wiki/Cross-site_request_forgery
    #
    # Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security blanket for your rails application.
    # There are a few guidelines you should follow:
    # 
    # * Keep your GET requests safe and idempotent.  More reading material:
    #   * http://www.xml.com/pub/a/2002/04/24/deviant.html
    #   * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
    # * Make sure the session cookies that Rails creates are non-persistent.  Check in Firefox and look for "Expires: at end of session"
    #
52
    module ClassMethods
53
      # Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
54
      #
D
David Heinemeier Hansson 已提交
55 56 57 58 59
      # Example:
      #
      #   class FooController < ApplicationController
      #     protect_from_forgery :except => :index
      #
60 61
      #     # you can disable csrf protection on controller-by-controller basis:
      #     skip_before_filter :verify_authenticity_token
D
David Heinemeier Hansson 已提交
62 63 64 65
      #   end
      #
      # Valid Options:
      #
P
Pratik Naik 已提交
66
      # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call.  Set which actions are verified.
67 68 69
      def protect_from_forgery(options = {})
        self.request_forgery_protection_token ||= :authenticity_token
        before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except)
70 71 72
        if options[:secret] || options[:digest]
          ActiveSupport::Deprecation.warn("protect_from_forgery only takes :only and :except options now. :digest and :secret have no effect", caller)
        end
73 74 75 76 77
      end
    end

    protected
      # The actual before_filter that is used.  Modify this to change how you handle unverified requests.
78
      def verify_authenticity_token
79
        verified_request? || raise(ActionController::InvalidAuthenticityToken)
80 81 82 83 84 85
      end
      
      # Returns true or false if a request is verified.  Checks:
      #
      # * is the format restricted?  By default, only HTML and AJAX requests are checked.
      # * is it a GET request?  Gets should be safe and idempotent
86
      # * Does the form_authenticity_token match the given token value from the params?
87
      def verified_request?
88 89 90
        !protect_against_forgery?     ||
          request.method == :get      ||
          !verifiable_request_format? ||
91
          form_authenticity_token == params[request_forgery_protection_token]
92 93 94
      end
    
      def verifiable_request_format?
95
        !request.content_type.nil? && request.content_type.verify_request?
96 97
      end
    
98 99
      # Sets the token value for the current session.  Pass a <tt>:secret</tt> option
      # in +protect_from_forgery+ to add a custom salt to the hash.
100
      def form_authenticity_token
101
        session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32)
102
      end
103

104 105 106
      def protect_against_forgery?
        allow_forgery_protection && request_forgery_protection_token
      end
107
  end
108
end