Unable to complete POST request in Clojure
Asked Answered
V

3

28

I have recently started exploring Clojure and I wanted to set up a simple web app with basic CRUD functionality. I found a nice tutorial here: http://www.xuan-wu.com/2013-09-21-Basic-Web-Application-in-Clojure.

The GET requests work fine, but whenever I attempt a post request, I get the following error:

Invalid anti-forgery token

The tutorial I mentioned earlier does not address anything security related. I did some digging around and it seems like I'm missing some component of Compojure that is supposed to generate a token for making POST requests. Some places mentioned that I was supposed to happen automatically without any changes on my part. I am still not sure what it is that I am missing. Here is my code:

(ns myblog.handler
    (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
            [myblog.views :as views]
            [myblog.posts :as posts]
            [ring.util.response :as resp]
            [ring.middleware.basic-authentication :refer :all]))

(defn authenticated? [name pass]
  (and (= name "user")
       (= pass "pass")))

(defroutes public-routes
    (GET "/" [] (views/main-page))
    (route/resources "/"))

(defroutes protected-routes
    (GET "/admin" [] (views/admin-page))
    (GET "/admin/add" [] (views/add-post))
    (POST "/admin/create" [& params]
        (do (posts/create params)
            (resp/redirect "/admin"))))

(defroutes app-routes
    public-routes
    (wrap-basic-authentication protected-routes authenticated?)
    (route/not-found "Not Found"))

(def app
  (wrap-defaults app-routes site-defaults))

Again, only the POST request "/admin/create" is failing with the invalid token error. Any idea what I'm doing wrong?

Victorvictoria answered 14/10, 2015 at 17:43 Comment(2)
how are you making the POST request? something like curl or in the browser?Robins
In the browser. I am submitting a form to /admin/create.Victorvictoria
D
37

Your problem is with the wrap-defaults and site-defaults setting. The site-defaults default configuration adds ant9forgery CSRF protection and any post request which does not include a valid CSRF-token will be blocked.

There are a couple of ways to get around this

  1. Use api-defaults instead of site-defaults. The api-defaults default config is for sites where you are serving up a web API and turns of the CSRF protection included in site-defaults, which is meant for a more traditional web site where post requests are generated by a form which was previously delivered via a get request and includes the csrf-token as a hidden field. The disadvantage of this solution is that it may also disable other middleware you do want to be included

  2. Disable the csrf protection in the site-default config. This involves setting the appropriate key value in the map to false i.e.
    (wrap-defaults app-routes (assoc-in site-defaults [:security :anti-forgery] false))

Dispread answered 15/10, 2015 at 3:8 Comment(0)
P
4

add a hidden field to your form, example use enlive-html:

(ns xxx.html
  (:use [net.cgrand.enlive-html])
  (:require [clojure.string :as str]
            [ring.util.anti-forgery :refer [anti-forgery-field]]))

(deftemplate render-page "base.html"
  [settings]
  [:form#my-form] (append (html-snippet (anti-forgery-field)))
  ...
)

or disable it, not recommend, as:

(def app (wrap-defaults app-routes (assoc-in site-defaults [:security :anti-forgery] false)))

Playlet answered 15/10, 2015 at 3:58 Comment(1)
If you're using Selmer the code for adding the the hidden field in your template would be {{anti-forgery-field|safe}} where :anti-forgery-field is a key in your data map.Blear
R
3

See this github example. Notably at the bottom:

ring-defaults includes anti-forgery by default for POST requests (and others that modify data).

I think you need to change your use of the defaults if you do not want this:

wrap-defaults routes site-defaults

If you don't want to disable it, you need to either submit a form with a hidden field with the token, or pass it in a header via X-CSRF-Token and X-XSRF-Token. See the ring middleware docs for ring-anti-forgery for details.

Robins answered 14/10, 2015 at 19:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.