Compojure routes with different middleware
clojure, compojure, middleware, ring, routes
Solution
(defroutes user-routes*
(GET "-endpoint1" [] ("USER ENDPOINT 1"))
(GET "-endpoint2" [] ("USER ENDPOINT 1")))
(def user-routes
(-> #'user-routes*
(wrap-basic-authentication user-auth?)))
(defroutes admin-routes*
(GET "-endpoint" [] ("ADMIN ENDPOINT")))
(def admin-routes
(-> #'admin-routes*
(wrap-basic-authentication admin-auth?)))
(defroutes main-routes
(ANY "*" [] admin-routes)
(ANY "*" [] user-routes)
This will run the incoming request first through admin-routes and then through user routes, applying the correct authentication in both cases. The main idea here is that your authentication function should return `nil` if the route is not accessible to the caller instead of throwing an error. This way admin-routes will return nil if a) the route actually does not match defined admin-routes or b) the user does not have the required authentication. If admin-routes returns nil, user-routes will be tried by compojure.
Hope this helps.
EDIT: I wrote a post about Compojure some time back, which you might find useful: https://vedang.me/techlog/2012-02-23-composability-and-compojure/
Problem
I'm currently writing an API in Clojure using Compojure (and Ring and associated middleware). I'm trying to apply different authentication code depending on the route. Consider the following code: ``` (defroutes public-routes (GET "/public-endpoint" [] ("PUBLIC ENDPOINT"))) (defroutes user-routes (GET "/user-endpoint1" [] ("USER ENDPOINT 1")) (GET "/user-endpoint2" [] ("USER ENDPOINT 1"))) (defroutes admin-routes (GET "/admin-endpoint" [] ("ADMIN ENDPOINT"))) (def app (handler/api (routes public-routes (-> user-routes (wrap-basic-authentication user-auth?))))) (-> admin-routes (wrap-basic-authentication admin-auth?))))) ``` This doesn't work as expected because `wrap-basic-authentication` indeed wraps routes so it gets tried regardless of the wrapped routes. Specifically, if the requests needs to be routed to `admin-routes`, `user-auth?` will still be tried (and fail). I resorted to use `context` to root some routes under a common base path but it's quite a constraint (the code below may not work it's simply to illustrate the idea): ``` (defroutes user-routes (GET "-endpoint1" [] ("USER ENDPOINT 1")) (GET "-endpoint2" [] ("USER ENDPOINT 1"))) (defroutes admin-routes (GET "-endpoint" [] ("ADMIN ENDPOINT"))) (def app (handler/api (routes public-routes (context "/user" [] (-> user-routes (wrap-basic-authentication user-auth?))) (context "/admin" [] (-> admin-routes (wrap-basic-authentication admin-auth?)))))) ``` I'm wondering if I'm missing something or if there's any way at all to achieve what I want without constraint on my `defroutes` and without using a common base path (as ideally, there would be none).