Search code examples
ruby-on-railscsrfauthenticity-token

How can you use Rails AuthenticityToken infrastructure to explicitly protect a GET action


Rails AuthenticityToken automatically protects POST/PUT/DELETE requests from CSRF attacks. But I have another use case in mind.

I am showing a video on my site that I don't want to be embeddable on other sites. How this works is that my flash player sends a request for a signed URL from my CDN that expires in a few seconds. Up until now a user had to be logged in to watch videos, so that was the authentication. However now I want any visitor to the site to be able to watch the video without allowing the signed URL to be requested from another site (such as if they embedded our player on their site).

My first thought went to AuthenticityToken since it seems to have these exact semantics... all I need to do is plug it into a GET request. Any ideas?


Solution

  • Rails, opinionated as it is believes that all GET requests should be idempotent. This means Rails of course does not check authenticity tokens for GET requests, even verified_request? gives every GET a pass.

    def verified_request?
      !protect_against_forgery?     ||
        request.method == :get      ||
        !verifiable_request_format? ||
        form_authenticity_token == params[request_forgery_protection_token]
    end
    

    So we have to write our own logic. We can use form_authenticity token. All this does is create a random string and cache it in the session:

    def form_authenticity_token
       session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32)
    end
    

    We can therefore make a before filter that tests the equality of a url parameter to the session token. Thereby ensuring that only bonafide visitors can view videos.

    Controller:

    class CDNController < ActionController::Base
      # You probably only want to verify the show action
      before_filter :verify_request, :only => 'show'
    
      # Regular controller actions…
    
      protected
    
      def verify_request
        # Correct HTTP response code is 403 forbidden, not 404 not found.
        render(:status => 403) unless form_authenticity_token == params[:token]
      end
    
    end
    

    The view:

    <%= video_path(:token => form_authenticity_token) %>