Friday, April 4, 2008

WTF is 422?

Blogging has been low on my priority list recently, since I've been working hard on my startup prototype, but I had to put in an entry tonight to share a solution to an unexpected problem I just fixed with my application.

So here's the scenario: My app needed to be able to send text messages, and receive texted responses to them for processing. Because I don't have an SMS gateway myself, I contracted to use the services of ipipi.com (see my post on SMS Gateway Providers). They provide an API of RESTful web services you can use for this sort of thing. They have a URL that I post my sent messages to, and I expose an address in my rails App that all incoming messages are forwarded to. Pretty straight-forward. Now, after reading the documentation for the web-service requirements, I quickly threw together some functional tests for my Rails Controller that would be handling incoming messages, and I'll be honest, I tested the bejeezus out of it. Every possible combination of bad parameters I could think of, pretty much anything that could go wrong I had a legitimate response for. So, after making sure my impressive suite of automated tests was passing successfully, I fired up my local server to give it a test run. I walked through the application, and received a text message on my phone, as intended. I couldn't respond to it directly, since my messages were being forwarded to my live application, but I had a mocked up form I could use to replicate the incoming HTTP-POST of a replying message, so I used it to generate a response, and everything behaved as expected. "Hooray", I thought, "I have most verily succeeded!".
Excited to try out the real response sent from my phone to the application, I promoted my code to my production server, and ran the test again. After recieving my text message, I responded, and waited....and waited...and waited. No dice. Apparently it just didn't get through. I went to check out my account with www.ipipi.com, assuming it must be some error in the forwarding settings, but everything checked out. Then I got an email in my gmail inbox: "Unable to forward SMS: Server Response - 422".
"422?", I thought, "What the hell is 422?". Obviously, it was an HTTP status code that was being returned, indicating some sort of error, but it wasn't one I was familiar with. A quick google search was very little help:

422 - Unprocessable Entity
The server understands the media type of the request entity,
but was unable to process the contained instructions.

This didn't really mean anything to me, so I had to operate on the assumption that, since my emulations of the POST were successful, something was wrong with ipipi.com. I wrote their customer service department, and was politely told that there was absolutely nothing wrong with their service and that the example I was using to emulate it was fine, so it was back on my plate.
I'll save you the story of the several hours that followed this. Suffice it to say that I did a lot of research before stumbling on the solution in an obscure message board. So here it is: 422 is returned by the Rails ActionController by default when a POST comes from another website outside your own web application. It's a security feature built in to prevent Cross-Site Request Forgery attacks. That's why my mocked-up emulator form post was working, it was originating from WITHIN my application, thus it had the necessary security key appended to the request, something that could not be done by an external application. So for those of you who are dying to know the solution, here's how you can disable this protection for a single method in a controller:


class CustomerController < ApplicationController

protect_from_forgery :secret => 'any_phrase',
:except => :web_service_method

def web_service_method
#..all your code for handling external post
end

end


the string attached to the "secret" symbol is just a string of your choice that will be used as the SALT for the creation of the security key used by all other controller methods. obviously, the "except" entry takes the name of the method that you wish to exclude from this forgery protection (thus allowing external sites to POST to it successfully. May this post help someone in the future from going through the same experience I had yesterday. ; )

6 comments:

Lee said...

You're a life saver mate! Thank you.

Anonymous said...

Thank you very much for posting this. Saved me a lot of time.

Aleksandr said...

THANK U MAN!!!!1

Duncan Bayne said...

If you are ever in Melbourne (Victoria, Australia) call me, and I'll come and find you, and buy you a beer.

Tristran said...

Absolute champion - been battling with this for ages.

Adam said...

Just wanted to let ya know that, even years later, your post is keeping me from pulling my hair out...it did the trick! Thanks much.