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.
Leave a Reply