Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Authorization header in Ruby on Rails accessed with key HTTP_AUTHORIZATION instead of Authorization?

I'm hoping someone can clear something up for me. I'm using Rails 2.3.5, and I can access request headers in a controller action like this:

def index   if request.headers['...'] == '...'     ...   end end 

Or something similar. request.headers is an instance of ActionController::Http::Headers which appears to be a Hash. I would expect, therefore, that headers are keyed on the name I send. If I send a request however, with an Authorization header, like so:

curl -H 'Authorization: OAuth realm="MyRealm",...' http://app/path 

The following code in the action returns false:

if request.headers.include?('Authorization') ...  

Whereas the following echos out the value I send in the header:

render :text => request.headers['Authorization'] 

The following check returns true, interestingly enough:

if request.headers.include?('HTTP_AUTHORIZATION') ...  

And similarly, the following echoes out the value I send in the header:

render :text => request.headers['HTTP_AUTHORIZATION'] 

Seems like there is some magic happening that I'm unaware of. I'm completely confused as to why checking for the key 'Authorization' fails, but rendering the value of request.headers['Authorization'] succeeds. I'm also confused as to where 'HTTP_AUTHORIZATION' is coming from as that is not the name of the header I'm sending with the request. Anyone know what's going on exactly?

like image 843
Paul Osman Avatar asked Feb 22 '10 15:02

Paul Osman


1 Answers

You are correct - the headers method of ActionController::Request returns an instance of ActionController::Http::Headers, which is inherits from Hash. If we crack open the source, we see this:

class Headers < ::Hash   extend ActiveSupport::Memoizable    def initialize(*args)      if args.size == 1 && args[0].is_a?(Hash)        super()        update(args[0])      else        super      end    end    def [](header_name)     if include?(header_name)       super     else       super(env_name(header_name))     end   end    private     # Converts a HTTP header name to an environment variable name.     def env_name(header_name)       "HTTP_#{header_name.upcase.gsub(/-/, '_')}"     end     memoize :env_name end 

So when accessing the Hash via [], there's a second check to see if value from env_name (which just upcases the key and prepends HTTP_) exists.

This is why you can't get a true value from request.headers.include?('Authorization') -- include? is not overridden in the subclass to check for both the normal and upcased version of the header. I imagine you could follow suit and implement it yourself like this:

module ActionController   module Http     class Headers < ::Hash       def include?(header_name)         self[header_name].present?       end     end   end end 

Throw that into lib/extensions/action_controller.rb or something, require it in environment.rb if necessary, and you should be good to go. I'd recommend just modifying your controller code to use [] and present? to do the check, though :)

The reason that the headers are upcased and prefixed with HTTP_, I believe, stems from Rack, Rails' HTTP middleware. It probably does this to remain impartial about case, additionally prepending HTTP_ to avoid conflicts with other non-header environment stuff that comes in.

So, yes, a bit magical, but not too hard to understand after glancing at the source, which I'd always recommend :) Rails has some very nice source that I've learned a lot from over the years.

like image 151
Brent Dillingham Avatar answered Sep 21 '22 04:09

Brent Dillingham