Search code examples
clojurecompojurering

How to authenticate a subset of routes in ring application?


I have two sets of compojure routes, public ones, which need no authentication, and private ones which need authentication.

(defroutes public-routes
  (GET "/" [] homepage-handler))

(defroutes private-routes
  (GET "/secrets" [] secrets-handler))

I created a middleware which checks is the user authenticated and either continues the middleware chain or raises.

(defn wrap-must-be-authenticated [handler]
  (fn [request]
    (if (authenticated? request)
      (handler request)
      (throw-unauthorized))))

(def app
  (-> private-routes
      (wrap-must-be-authenticated)))

This works fine, all "private routes" require authentication.

How would I go about adding the public-routes so they are excluded from wrap-must-be-authenticated?

I believe defroutes returns ring handlers, so I'm thinking I need to do something like:

(-> (wrap-must-be-authenticated private-routes)
     public-routes)

Solution

  • One way to do this is to put multiple routes definitions in a containing routes, and wrap (wrap-routes) the appropriate routes in middleware to restrict access:

    (def all-routes
      (routes
        (-> #'private-routes
            (wrap-routes wrap-must-be-authenticated))
    
        #'public-routes
    
        (route/not-found
          (:body
            (error-page {:status 404
                         :title "page not found"})))))
    

    Another example from a project where I'm using buddy.auth's restrict:

    (defn wrap-admin [handler]
      (restrict handler {:handler (fn [req]
                                    (boolean (get-in req [:session :admin?])))}))
    
    (def app-routes
      (routes
        (-> #'admin-routes
            (wrap-routes wrap-admin)
            (wrap-routes middleware/wrap-csrf)
            (wrap-routes middleware/wrap-formats))
        (-> #'home-routes
            (wrap-routes middleware/wrap-csrf)
            (wrap-routes middleware/wrap-formats))))