CSRF protection in ring

There are several security attacks that frameworks like Rails try to guard against. In clojure, the choice of using several small libraries instead of a framework means we have to work to protect ourselves. This post will cover how to protect against CSRF attacks.

James Reeves has written ring-anti-forgery, a middleware that will intercept POSTs, PUTs, PATCHes, and DELETEs and check for an anti-forgery token.

The basic example of usage from the readme:

(use 'ring.middleware.anti-forgery
     'ring.middleware.session)
     
(def app
  (-> handler
      wrap-anti-forgery
      wrap-session))

Using ring-anti-forgery requires also using a session middleware in order to save the expected anti-forgery token across requests. Attempting to send a form request at this point should present an error page. Customization of the error is handled by passing options to wrap-anti-forgery as described in the readme.

In order for form requests to succeed, the form needs to include the anti-forgery token as a parameter. A function ring.util.anti-forgery/anti-forgery-field is defined to create html for a hidden field with the token. Additionally, ring.middleware.anti-forgery/*anti-forgery-token* contains the token for the request, and can be used in your templates. For example, in clojars I've written a helper that will automatically add the token. It works as a drop-in replacement for hiccup's form-to

(defmacro form-to
  "Create a form that points to a particular method and route.
  e.g. (form-to [:put \"/post\"]
  ...)"
  [[method action] & body]
  `(if (contains? #{:head :get} ~method)
     (form/form-to [~method ~action] [email protected]body)
     (form/form-to [~method ~action]
       (conj
         (form/hidden-field "__anti-forgery-token"
                            anti-forgery/*anti-forgery-token*)
         [email protected]body))))