[Revised: found out my first diagnosis and fix were incorrect, but eventually I tracked down the real answers.]
For my summer project, I'm developing a web site that local food banks can use to let people know what items they currently need. Long term, I want this to be accessible via smart phones, but for now I'm just getting started with the basics. I'm using the Luminus framework to kick things off since it's so convenient and well-documented.
I initially set up my site with support for DailyCred, which I only heard about because it was one of the template options for creating a new Luminus project. It seemed pretty easy to set up, and I got as far as redirecting users to DC and getting back the token, but at that point DC would complain that I was using an unknown client ID (even though it was a valid ID that worked correctly in their sandbox), and I'd be dead in the water. Finally I decided to fall back to a more conventional approach: today's project is to rip out all the DC stuff and replace it with Friend. Partly this was frustration with DC, and partly it was because I just got done watching this video, and wanted to take Friend for a spin.
Friend was easy to add to my project. I had some initial difficulty with my project crashing ("can't find ring.util.response..."), but this turned out to be a known issue that is easily solved by listing the friend dependency before the compojure dependency. Not ideal, but livable.
My next issue was that I wanted to require https for the registration and login pages (obviously). My main non-https app is running on port 3333, so I thought I could lock down my two secure pages with this:
Unfortunately, that seems to make https required site-wide, which is not what I had in mind at all. The gotcha is that friend/requires-scheme is middleware: as each request is passed through the chain of handlers, any middleware around those handlers will be executed—whether any handlers inside the middleware are triggered or not. The requires-scheme middleware was checking whether the request was https, and redirecting if it wasn't, regardless of whether the incoming URL matched the restricted list or not.
The solution, of course, is to move the restricted URL's after all the open URLS.
Now (in theory) my login and register pages should require https, while my front page and misc. pages should accept either protocol (except for one big gotcha, solved below).
Of course, that assumes I have https set up, which is a real pain in Java/Jetty. Luckily, Luminus has an option to install http-kit to use instead of ring-jetty, and it's Ring/Compojure-compatible. The trick is to use http-kit to talk to an nginx reverse proxy, since it's a lot easier to configure SSL under nginx. Also, if you're on a reasonably recent Ubuntu (like I am) there's already an SSL certificate built in that you can just pass to nginx. You'll get a security warning when you visit your site—this is a dev-only setup—but at least you can get off the ground. Here's my nginx config file. My Ring app is listening on port 33333, and nginx is listening on 3333 (non-secure) and 3334 (secure).
The last lingering gotcha here is that nginx passes all requests through as http. It's only on localhost, so that's reasonably ok, except that it totally breaks friend/requires-schema! The Friend middleware is checking whether the request is https, and it never will be. Don't worry, Friend has this base covered too: use the friend/requires-schema-with-proxy function instead, and it will look for the X-Forwarded-Proto header that nginx is sending (you'll see it in the config file above). Like the source code notes, you should never use this on a front-facing production server, because X-Forwarded-Proto is trivial to fake.
That's got me set up with at least the beginnings of my security system. Next, I think I'll have a look at the 4clojure login code and see what useful tidbits I can pick up there.
No comments:
Post a Comment