Nesting site/api routes with Friend

Skip to first unread message

Jonathon McKitrick

Aug 8, 2014, 10:34:10 AM8/8/14
I'd like to have my site routes and an api route with an 'api/' context.  I have that working fine.  But when I add Friend authentication, it breaks things.

The main site pages only work with Friend if they are listed first in the routes.  But in that case, the 'api/' context routes are not matched when they are requested.

Any suggestions?

(defroutes api-routes
  (context "/api" []
;; All api routes here
           (route/not-found "ERROR")))

(defroutes www-routes
  (GET "/admin" req (friend/authorize #{::admin} "Admin only"))
  (GET "/authorized" req (friend/authorize #{::user} "Users only"))
  (GET "/home" [] (response/file-response "home.html" {:root "resources/public"}))
  (GET "/login" [] (response/file-response "login.html" {:root "resources/public"}))
  (friend/logout (ANY "/logout" req (response/redirect "/")))
  (GET "/" [] (response/redirect "index.html"))
  (route/resources "/")
  ;;(route/not-found "Not Found")

(def app
   (-> www-routes
       (friend/authenticate {
                             :credential-fn (partial creds/bcrypt-credential-fn users)
                             :workflows [(workflows/interactive-form)]})
   (-> api-routes

James Reeves

Aug 8, 2014, 3:07:01 PM8/8/14
to Compojure
Hi Jonathan

There are a few problems with your routes and the order in which you apply middleware.

It's important to realise that the routing mechanism for Compojure is very simple. The request is sent to each route in order, until a route returns a non-nil response.

In your case, you've set it up so that every single request is sent through the authenticate middleware, regardless of whether it's for the api-routes or www-routes. You also apply middleware twice, which can be a problem when consuming streams.

Your app wants to look more like:

(def app
   (context "/api" [] (handler/api api-routes))
   (handler/site (friend/authenticate www-routes auth-options)
   (route/not-found "Not found")))

If you pass this code an api request, it applies the api middleware, then heads into the api-routes.

If you pass this code a www request, it skips the api-routes due to the context, then applies the site and authenticate middleware, then heads into the www-routes.

Incidentally you could write your www-routes like:

(def www-routes
   (GET "/admin" [] (friend/authorize #{::admin} "Admin only"))
   (GET "/authorized" [] (friend/authorize #{::user} "Users only"))
   (GET "/home" [] (io/resource "public/home.html"))
   (GET "/login" [] (io/resource "public/login.html"))
   (GET "/" [] (io/resource "public/index.html"))
   (friend/logout (ANY "/logout" [] (response/redirect "/")))
   (route/resources "/")))

The function will return a URL to a resource, which Compojure can use to serve as a response.

- 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
To post to this group, send email to
Visit this group at
For more options, visit

Jonathon McKitrick

Aug 8, 2014, 3:41:26 PM8/8/14
First of all, thanks, it all works, after a minor tweak to wrap-restful-format on the api handlers.

But how can I make sure the api calls are authenticated as well?

James Reeves

Aug 8, 2014, 3:58:15 PM8/8/14
to Compojure
You could just add authentication middleware to your api-routes as well as the www-routes. You probably want different authentication options for your API routes anyway.

- James

Jonathon McKitrick

Aug 8, 2014, 5:26:59 PM8/8/14
Odd that wasn't working before (wrapping each handler separately) but it is now.  Thanks!

On Friday, August 8, 2014 10:34:10 AM UTC-4, Jonathon McKitrick wrote:
Reply all
Reply to author
0 new messages