views:

288

answers:

2

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?

+1  A: 

To plug the authenticity token in your url:

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

In your CDN's controller, you could check if the authenticity token is correct with a before_filter:

def verify_token
    render_404 unless form_authenticity_token == params[:token]
end

def render_404
    render :file => "#{RAILS_ROOT}/public/404.html", :status => 404
end
agregoire
+2  A: 

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) %>
Steve Graham