0
votes

I'm getting a baffling behavior when using Ring where POST requests all give "Invalid anti-forgery token" but only do this when I have the handler behind a function.

So,

(def app app-routes)

works fine.

Whereas

(defn app [args] 
  (app-routes args))

throws anti-forgery errors for POST requests.

Minimal code example. :

(defroutes app-routes
  (GET "/login" request (fn [req]
                          (html5
                            [:body
                             [:div
                              [:form {:action "/login" :method "post"}
                               (anti-forgery-field)
                               [:input {:name "username"}]
                               [:input {:name "password"}]
                               [:button {:type "submit"} "submit"]]]])))

  (POST "/login" request (fn [req] "Yay!")))


; WORKS A-OK like this
(def app (-> app-routes (wrap-routes site-defaults)))

In my project.clj

:plugins [[lein-ring "0.12.5"]]
:ring {:handler myapp.application/app

But again, if I change this to be a function:

(defroutes app-routes
  (GET "/login" request (fn [req]
                          (html5
                            [:body
                             [:div
                              [:form {:action "/login" :method "post"}
                               (anti-forgery-field)
                               [:input {:name "username"}]
                               [:input {:name "password"}]
                               [:button {:type "submit"} "submit"]]]])))

  (POST "/login" request (fn [req] "Yay!")))


; ALL POST REQUESTS FAIL 
(defn app [args] 
  (let [handler (-> app-routes (wrap-routes site-defaults)))]
    (handler args)))

the all post requests fail due to forgery tokens. All GET requests work fine.

Stranger still: I thought maybe it didn't like being re-initialized, so I stuffed it into an atom to make it more singleton-like

(def handler (atom nil))
(defn app [args] 
  (swap! handler #(when (nil? %)
                    (-> app-routes
                        (wrap-defaults site-defaults))))
  (@handler args))

Now GET and POST requests work as expected. Except now wrap-reload (not pictured above) throws exceptions!

I have no idea why it is behaving this way, and have been completely unable to debug. Could anyone shed some light?

Edit:

For context on why I'm trying to wrap it at all: I want to be able to wire up dependencies (like database connections) and then inject them into the controllers used by the routes .

1
I notice that some of your wraps are (wrap-routes site-defaults) rather than (wrap-defaults site-defaults).clartaq

1 Answers

0
votes

I'm not an expert on wrap-routes, but there are clear clues to your problem.

In method #1 with def, you create a handler and assign it to the var attached to the symbol app.

In method #2 (failing), you define a function app. Since handler is now in the let form, you are creating a new handler on each function call. I'd wager that is the source of the problem.

In method #3, the handler atom is only initialized upon the first call, and never changed after that (because of the nil? test).

Since it works in #1 and #3 when the handler is only created once, it seems clear that creating a new handler on each call in #2 is the problem. Since the anti-forgery token is basically a random number that is different on each call to wrap-routes, you never have the same token used by the POST that was generated by the GET route.


Re your larger goal: You may find it easier to use the Pedestal framework to inject the desired dependencies into the request. Or, you could write a simple Compojure handler (really just a function that takes a handler and returnes a wrapped version of that handler). Probably only 5-10 lines of code.

You will also probably like the book Web Development with Clojure.

Here is a list of other documentation.