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).

Original source