Stuart Hinson bio photo

Stuart Hinson

Ruby, Rust & Clojure programmer.

stuarthinson<at>gmail

LinkedIn Github

OWASP has an excellent description of CSRF attacks and why they’re a concern. The tl;dr is, without safegaurds, an attacker can cause users to take unintended actions on your app. It’s well worth spending a few minutes on the link above if you’re not familiar.

Using io.pedestal.http.csrf to prevent attacks

Pedestal’s io.pedestal.http.csrf namespace implements the synchronizer token pattern that gives us the tools we need.

We’ll start with a blank pedestal-service template

lein new pedestal-service csrf

And add Pedestal’s CSRF namespace to our service

(ns csrf.service
  (:require [io.pedestal.http :as bootstrap]
            [io.pedestal.http.route :as route]
            [io.pedestal.http.body-params :as body-params]
            [io.pedestal.http.route.definition :refer [defroutes]]
            [io.pedestal.http.csrf :as csrf] ;; add this
            [ring.util.response :as ring-resp]))

The trick to using csrf/anti-forgery with a form parameter (as of 0.3.1) is to realize that the io.pedestal.http.body-params/body-params interceptor, which is responsible for parsing parameters encoded in the request body, is not included by io.pedestal.http/default-interceptors. That means naively enabling CSRF protection in your service map, e.g.

(def service {...
              ::bootstrap/enable-csrf {} ;; doesn't work with form params!
              ...})

won’t work because the anti-forgery interceptor will attempt to read from form-params that haven’t been parsed by body-params yet. Resolving it is simple, we’ll update our service definition to include

(def service {...
              ::bootstrap/enable-session {}
              ...})

and include our csrf middleware on the appropriate routes after io.pedestal.http.body-params/body-params has been applied

(defroutes routes
  [[["/"
     ^:interceptors [(body-params/body-params)
                     bootstrap/html-body
                     (csrf/anti-forgery)]
     ["/csrf-protected "{:post csrf-protected-handler}]]]])

All that’s left is to include the request’s anti-forgery token in our forms, something like

(format "<input type=\"hidden\" name=\"__anti-forgery-token\" value=\"%s\"/>"
  (::csrf/anti-forgery-token request))

A full sample’s available on Github.