Microsoft Office links causing InvalidAuthenticityToken in Rails

March 04, 2009

I started receiving a lot of error notifications recently from my ExceptionNotfier plugin for an error with ActionController::InvalidAuthenticityToken. It turned out the error was occurring because one of my users was pasting a link to my app in an MS Office document and when Office sees the link it makes a request that Rails could not handle. Here I'll show you a simple fix you can use to avoid these errors with much credit going to an article at
Dealing with Microsoft Office Protocol Discovery in Rails

My execptions looked something like this (lots of boring details omitted)

A ActionController::InvalidAuthenticityToken occurred in events#1164:

[RAILS_ROOT]/vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb:86:in `verify_authenticity_token'

* HTTP_USER_AGENT : Microsoft Data Access Internet Publishing Provider Protocol Discovery

The problem is that Rails doesn't understand the method 'OPTIONS' (see rails/actionpack/lib/action_controller/routing.rb

#around line 270 of routing.rb
module ActionController
  module Routing
    HTTP_METHODS = [:get, :head, :post, :put, :delete]

Fixing the problem is fairly simple. You insert a before_filter into your application controller to intercept and handle requests with the option method before the rails code realizes it can't handle the request.

class ApplicationController < ActionController::Base
  before_filter :options_for_microsoft_office_protocol_discovery

  ### Lots of code omitted

  def options_for_microsoft_office_protocol_discovery
    render :nothing => true, :status => 200 if request.method == :options

Its also easy to write a simple spec in rspec to verify the behavior. There is one trick which is that rails/actionpack/lib/action_controller/test_process.rb defines helper methods for get, post, put, delete & head that we can't use so we need to call the underlying method directly but the signature for that underlying method changed with Rails 2.3 (commit) so depending what version you're using you'll need one of 2 flavors.

# Rails 2.3 and above 
it 'should not throw an exception on OPTIONS request (from ms office protocol discovery)' do
  process '/any/goofy/path', nil, nil, nil, 'OPTIONS'
  response.should be_success

# Rails < 2.3 version
it 'should not throw an exception on OPTIONS request (from ms office protocol discovery)' do
  @request.env['REQUEST_METHOD'] = 'OPTIONS'
  process '/any/goofy/path'
  response.should be_success

You can also test from the command line using curl

curl -X OPTIONS http://localhost:3000/

Update: It turns out that Rails stores the acceptable methods in 2 different places actionpack/lib/action_controller/request.rb which does include all of get head put post delete options and also (see rails/actionpack/lib/action_controller/routing.rb which only includes :get, :head, :post, :put, :delete (options is missing). This means this fix will only work for OPTIONS requests and not any other type as ActionController::Request request_method will throw an exception before getting to the filter code above.