We tend to be very security conscious at Carousel Apps and one thing we often do is force all our applications to run over TLS (aka SSL), that is, when you go to http://example.com we redirect you to https://example.com. This little article will show you how to do it in a Luminus application.

First, add Ring SSL to your project by editing project.clj , finding the list of dependencies and adding:

[ring/ring-ssl "0.2.1"]

That library provides various Ring middleware functions to deal with SSL. The first one you care about is wrap-ssl-redirect  that will cause an HTTP request to be redirected to the equivalent HTTPS one.

In your middleware.clj  file, find the function wrap-base  which will look something like:

(defn wrap-base [handler]
  (-> handler
      wrap-dev
      wrap-auth
      (wrap-idle-session-timeout
        {:timeout          (* 60 30)
         :timeout-response (redirect "/")})
      wrap-formats
      (wrap-defaults
        (-> site-defaults
            (assoc-in [:security :anti-forgery] false)
            (assoc-in [:session :store] (memory-store session/mem))))
      wrap-servlet-context
      wrap-internal-error))

Depending on which options you added, you might have fewer or more middleware functions in your project. You can start simple by adding wrap-ssl-redirect  to it, like this:

(defn wrap-base [handler]
  (-> handler
      wrap-ssl-redirect
      wrap-dev
      ...))

Redirecting to SSL should happen as early as possible to avoid leaking any information over a non-secure channel. With that code you’ll immediately run into trouble when running your project locally, because neither your development environment nor your test one are likely to support TLS, so you’ll get redirected to HTTPS and it won’t work.

That’s easy to solve by creating a function, let’s call it enforce-ssl , that will skip the enforcement when developing or testing:

(defn enforce-ssl [handler]
  (if (or (env :dev) (env :test))
    handler
    (-> handler
        wrap-ssl-redirect)))

and then in your middleware configuration:

(defn wrap-base [handler]
  (-> handler
      enforce-ssl
      wrap-dev
      ...))

The function wrap-ssl-redirect obviously checks whether you are already accessing the site over SSL and if that’s the case, it doesn’t redirect you; otherwise you’d have a redirect loop.

If your application is sitting behind a proxy or a load balancer, like in the case of Heroku, Linode’s Load Balance, AWS Elastic Load Balancing, etc. the proxy will be terminating the HTTPS connection and then it’ll issue an HTTP (non-S) connection to your application because since now you are in an internal secure network, encryption is no longer required and HTTP is faster. Thus, your application after forwarding to HTTPS will still receive connections to HTTP and forward again and it’ll be stuck in an infinite forwarding loop.

The convention for this situation is that those proxies will add the header X-Forwarded-Proto with the original protocol, either http  or https. There’s another middleware function called wrap-forwarded-scheme  that will look at X-Forwarded-Proto (or another header entry of your choosing) and set that as the actual scheme in the request so a simple check for http  or https would suffice:

(defn enforce-ssl [handler]
  (if (or (env :dev) (env :test))
    handler
    (-> handler
        wrap-ssl-redirect
        wrap-forwarded-scheme)))

Doing the scheme forwarding should be the first thing you do, so that wrap-ssl-redirect  can use the correct scheme.

IMPORTANT: if your application is not behind a trusted proxy, or the proxy is using a different header, then blindly applying wrap-forwarded-scheme would be dangerous as your application could be easily deceived into believing a connection is secure which would enable a man‐in‐the‐middle attack.

One last thing that we do is add wrap-hsts  which makes sure the browser from now on will only use HTTPS for this domain, even if the user types http (which would result in a redirect anyway). The final code looks like this:

(defn enforce-ssl [handler]
  (if (or (env :dev) (env :test))
    handler
    (-> handler
        wrap-hsts
        wrap-ssl-redirect
        wrap-forwarded-scheme)))

And that’s it, your application now uses TLS/SSL by default, something that you should always consider and definitely do if there’s any kind of auth at all.

Advertisements

One thought on “Forcing SSL in a Luminus application

  1. I know you wrote this 18 months ago, but a lot of this is built-in to the ring defaults now, by setting the :security and :proxy config params to true. Which I’ve just spent all day discovering :(

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s