Skip to the content.

Form Submission

Let’s talk about form submission. Usually it wouldn’t matter, but since we’ve been hacking forms into the web for that last few decades, things have gotten a little complicated. We have things like XSS and CSRF which thankfully are built into joy.

Submitting a form with no protection

This is pretty much all you need to have a working form

[:form {:method "post" :action "/"}
  [:input {:type "text" :name "username"}]]

This outputs

<form method="post" action="/">
  <input type="text" name="username" />
</form>

Of course this is vulnerable to CSRF so we should soup it up a little bit

(import joy)

(defn new [request]
  (joy/form-for [request :create]
    [:input {:type "text" :name "username"}]))

(joy/defroutes routes
  [:get "/" new])

At this point you’re going to need a .env file with a CSRF_TOKEN_KEY variable in it or set a CSRF_TOKEN_KEY in your os environment:

(import cipher)

(os/setenv "CSRF_TOKEN_KEY" (cipher/encryption-key))

This is looking much better, along with the rest of a few of joy’s middleware functions this will send a csrf token along with the form:

<form method="post" action="/">
  <input type="text" name="username" />
  <input type="hidden" name="csrf-token" value="some long base64 encoded string" />
</form>

I waved away some of the complexity that’s associated with generating that token, but a more complete example is below:

(import joy)
(import json)

(defn create [{:body body}]
  (let [name (get body :username)]
    (joy/render :json (json/encode {:username name}))))

(defn new [request]
  (joy/form-for [request :create]
    [:input {:type "text" :name "username"}]))

(joy/defroutes routes
  [:get "/" new]
  [:post "/" create])

(def app (-> (joy/handler routes)
             (joy/csrf-token)
             (joy/sessions)
             (joy/body-parser)))

There we go, a few bells and whistles and middleware and now form-for is (hopefully) as secure as it can be! The CSRF token is sent with the form and encrypted in the session cookie.

Also body-parser was included because otherwise we can’t actually parse the body, go figure.