Applying different middleware for routes

250 views
Skip to first unread message

Karolis Labrencis

unread,
Aug 20, 2016, 10:01:37 AM8/20/16
to Compojure
Hello,

I am trying to have both api and site routes, but `site-defaults` gets applied to my api routes too, so I cannot POST to api routes because of CSRF protection. Is there some other way to combine two handlers with different 'defaults'?

My routes:

(ns hello-world.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults api-defaults]]
            [ring.util.anti-forgery :refer [anti-forgery-field]]))

(defroutes app-routes
           (GET "/" [] (str "<html><form action=\"post\" method=\"post\"><input name=\"id\"/>"
                            (anti-forgery-field)
                            "<input type=\"submit\"/></form></html>"))
           (POST "/post" [id] id))

(defroutes api-routes
           (POST "/api/post" [] "Hello from api!"))

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

Thank you!

James Reeves

unread,
Aug 20, 2016, 10:37:40 AM8/20/16
to Compojure
It's important to first understand that some middleware is side-effectful, because it consumes the request body, and the request body is an input stream. In your code you apply wrap-params twice; once via site-defaults, and once via api-defaults. The site-defaults wrap-params will consume the body, leaving nothing left for api-defaults.

So the first change is to ensure that all common middleware is factored out. Fortunately, site-defaults is a superset of api-defaults, so we can write:

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

The next problem we have is how to deal with wrap-anti-forgery. If this detects a POST without the anti-forgery-token, it will return an access error, but crucially it checks before the routes are matched, because middleware have no concept of route matching. This means it will return an access error even if the route is not in app-routes.

The simplest way around this is to put api-routes first:

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

Another way is to use the compojure.core/wrap-routes function, which applies middleware after the route has been matched, but before its body has been processed. For example:

  (-> app-routes
      (wrap-routes wrap-anti-forgery)
      (wrap-defaults (dissoc-in site-defaults [:security :anti-forgery]))

- James

--
You received this message because you are subscribed to the Google Groups "Compojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to compojure+unsubscribe@googlegroups.com.
To post to this group, send email to comp...@googlegroups.com.
Visit this group at https://groups.google.com/group/compojure.
For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages