the-frey~/blog

Composable Liberator Resources

Using Liberator maps in practice

14 September 2016

I touched on this in my last post, but I thought it might be useful to provide another example. Here’s where we were up to last time.

I’ve slightly changed hello-world-map to accept JSON, for reasons we’ll see later. We’ve also changed the route for that map to /hello:

NB: other dependencies are required here, and I’ve not included them all, just the liberator/ring specific ones.

So let’s say we want to implement an API, and we’ve got users with API keys that will be passed along with a request. Now, this isn’t super-hard to do in a framework like Rails, but I guarantee you that it can’t be done as elegantly as in the simple Clojure version that follows.

First, let’s use the ring basic-authentication-request function to help us build a token auth fn.

basic-authentication-request takes a request and a function that checks whether the user is authorized. The API key will need to be passed base64-encoded in an authorization header.

It returns a key, :basic-authentication if successful, as can be seen in the source:

(defn basic-authentication-request
  "Authenticates the given request against using auth-fn. The value
  returned by auth-fn is assoc'd onto the request as
  :basic-authentication.  Thus, a truthy value of
  :basic-authentication on the returned request indicates successful
  authentication, and a false or nil value indicates authentication
  failure."
  [request auth-fn]
  (let [auth ((:headers request) "authorization")
        cred (and auth (decode-base64 (last (re-find #"^Basic (.*)$" auth))))
        [user pass] (and cred (s/split (str cred) #":" 2))]
    (assoc request :basic-authentication (and cred (auth-fn (str user) (str pass))))))

The auth-fn we are using accepts an email and token and checks whether the user contains a :token or :api-key field that matches (this is a monger resource, in case you’re curious). The idea here is that you could say use one for a form field login, and the other for the API.

Note that it’s good practice to include a constant time checker (here represented by ===) to avoid timing attacks.

Now, we’re ready to write a Liberator resource map that we can then call from a compojure route.

Let’s say that our hello-world-map from the previous example is in a ns called hello under routes.

Whoa! That wasn’t very painful, was it? So now if you request /hello (maybe there’s session-based auth going on in a middleware elsewhere) you don’t expressly need a base64 encoded authorization header, as you now do with /api/hello.

You could also completely refactor the original map to handle auth within the Liberator map, using a similar or different pattern. An alternative use would be a def called map-defaults with common settings for all routes, which would be merged with other functionality like our :authorized? example above.

Either way, ignoring the somewhat contrived example, it’s a cool and useful pattern that I hope I’ve demonstrated.

Fork me on GitHub