Search code examples
ruby-on-railsapisslherokuhttp-status-code-301

Rails App HTTP to HTTPS Redirect Not Working for Exposed API


I am building a Rails 4 app with both a front-end website and an exposed API. The API serves as the backend for a mobile app and a desktop app.

I deployed the app on Heroku. Because the website has an ordering system, I decided to force ssl globally. When I make HTTP requests, the site successfully redirects me to HTTPS.

However, when I make HTTP requests to my exposed API using cURL from the command line, I don't get the expected response. Instead I see a 301 status message in my Heroku logs (see below). When I make the same request with cURL over HTTPS, everything works and I get a successful response.

So HTTP requests are being redirected to HTTPS successfully everywhere but the API. However, the API still responds correctly to HTTPS requests.

What am I doing wrong? How can I allow users to make HTTP requests to my API, but simply redirect their requests over HTTPS like in the rest of the app? Does this not make sense?

I am using this to force https in my controller:

# /app/controllers/api/v1/pairings_controller.rb
before_filter :redirect_to_https
...
def redirect_to_https
  redirect_to :protocol => "https://" unless (request.ssl? || request.local?)
end

and I've also tried

# config/application.rb
config.force_ssl = (ENV["ENABLE_HTTPS"] == "yes")
config.ssl_options = {hsts: {expires: 1209600}}

Heroku logs:

Unsuccessful with HTTP:

2015-03-20T13:40:26.590091+00:00 heroku[router]: at=info method=POST path="/api/v1/pairing/request" host=xxx.herokuapp.com request_id=f832239b-d352-43ed-b5c6-e8d6c9f7c242 fwd="23.27.14.153" dyno=web.1 connect=1ms service=11325ms status=200 bytes=540
2015-03-20T13:40:26.585221+00:00 app[web.1]: Completed 200 OK in 4954ms (Views: 0.3ms | ActiveRecord: 20.3ms)
2015-03-20T13:45:43.502413+00:00 heroku[router]: at=info method=POST path="/api/v1/pairing/request" host=xxx.herokuapp.com request_id=dd633b85-a70b-4a2d-bb39-b450972bb2ab fwd="23.27.14.153" dyno=web.1 connect=1ms service=1739ms status=301 bytes=211

Successful with HTTPS:

2015-03-20T13:40:19.113646+00:00 app[web.1]: Started POST "/api/v1/pairing/request" for 23.27.14.153 at 2015-03-20 13:40:18 +0000
2015-03-20T13:40:21.064630+00:00 app[web.1]: Processing by Api::V1::PairingsController#post_pairing_request as JSON
2015-03-20T13:40:21.631071+00:00 app[web.1]:   Parameters: {"flags"=>"xxx", "otp"=>"xxx", "phone"=>"xxx", "pin"=>"xxx", "user"=>{"address"=>"xxx", "city"=>"Ripley", "email"=>"xxx", "name"=>"xxx", "state"=>"xxx", "zip"=>"xxx"}, "pairing"=>{"pin"=>"xxx", "phone"=>"xxx", "otp"=>"xxx"}}
2015-03-20T13:40:26.590091+00:00 heroku[router]: at=info method=POST path="/api/v1/pairing/request" host=xxx.herokuapp.com request_id=f832239b-d352-43ed-b5c6-e8d6c9f7c242 fwd="23.27.14.153" dyno=web.1 connect=1ms service=11325ms status=200 bytes=540
2015-03-20T13:40:26.585221+00:00 app[web.1]: Completed 200 OK in 4954ms (Views: 0.3ms | ActiveRecord: 20.3ms)

Solution

  • The problem is not with your Rails app - it's responding correctly with a 301 redirect message to connections that don't use https.

    It's up to the client to follow 301 redirects. My guess is that your web browser follows the redirect, but cURL doesn't.

    Perhaps this can help: Is there a way to follow redirects with command line cURL