0

I have the following setup:

  1. Macchiato framework for the backend (i.e. ClojureScript on node.js), which models handling on Ring.
  2. Frontend consisting of a small React App to edit data stored in an atom (tracking various inputs).

I want to make the data stored in that atom persistent by posting it to a handler in my back end. The most straightforward way of doing this would seem to just take the data straight from the atom and do the following:

(http/post "https://post.here.url"
                                {:edn-params
                                 @my-atom})

However: By default Macchiato requires POST requests to include an anti-forgery token (which I'm currently storing as an attribute to one of my HTML elements; please tell me in case this is bad practice). So I tried the following:

(http/post "https://post.here.url"
                                {:edn-params
                                 {:data @my-atom
                  :__anti-forgery-token "SuperSecretToken"}})

This doesn't work, however, since the token is rejected as invalid. The anti-forgery token seems to be processed correctly only if I declare the data as :form-params:

(http/post "https://post.here.url"
                                {:form-params
                                 {:data (str @my-atom)
                  :__anti-forgery-token "SuperSecretToken"}})

The method above does work, but ,of course, the MIME type is not set correctly and I have to do some hula-hooping to make the EDN data available on the server side. The approach simply does seem wrong. Is there a way to serialize the EDN data properly and still transmit the anti-forgery token?

I'm still quite new to this stuff, so maybe I'm missing something basic. Am I wrong about the purpose of the anti-forgery token in general? Does it only make sense when transmitting form data (which my data actually is; it's just that posting the atom directly would make reloading the stored data much easier).

Many thanks for any input you may give me!

Oliver

Phylax
  • 189
  • 10

1 Answers1

1

This is a bit tricky to figure out with how the docs are set up right now, but here are 3 useful pieces of information:

  1. https://macchiato-framework.github.io/api/core/macchiato.middleware.anti-forgery.html
  2. https://github.com/macchiato-framework/macchiato-core/blob/master/src/macchiato/middleware/defaults.cljs#L83
  3. https://macchiato-framework.github.io/docs/restful-middleware.html

If you use the wrap-defaults middleware wrapper then, you can pass in an options value, which is either truthy (enabling the default anti forgery behavior as you observe), or an option map, which the doc in [1] says takes an optional :read-token function.

Put together, plus the rest-middleware in [3], here's one way to implement the anti-forgery check that accepts the default (form params) or a json payload (body-params):

(-> handler
    ;; note: the wrapped middleware request processing
    ;; is handled _bottom-up_ in this chain
    (defaults/wrap-defaults
     (-> defaults/site-defaults
         (assoc-in [:security :anti-forgery]
                   {:read-token (fn [request]
                                  (or
                                   ;; check the custom key we set upstream (but in the block below)
                                   (get-in request [:anti-forgery-payload])
                                   ;; default behavior
                                   (anti-forgery/default-request-token request)))})))
    (middleware/wrap
     (fn extract-anti-forgery-payload [handler]
       (fn [request respond raise]
         (handler
          (if-let [anti-forgery-payload
                   (get-in request [:body "__anti-forgery-token"])]
            (-> request
                ;; add our custom anti-forgery key to the request
                (assoc :anti-forgery-payload anti-forgery-payload)
                ;; remove the special key from the request to downstream handlers
                (update :body dissoc "__anti-forgery-token"))
            request)
          respond raise)))
     {})
    ;; decodes the json input into maps
    (macchiato.middleware.restful-format/wrap-restful-format))
whacked
  • 11
  • 1