liberator-friend that shows off my final solution: Liberator resources that delegate to Friend into the authorized? point. The code is on GitHub.

The example project defines a Friend base resource that provides a handler that Liberator calls when :authorized? returns false:

( def friend-resource "Base resource that will handle authentication via friend's mechanisms. Provide an authorization function and you'll be good to go." { :base base-resource :handle-unauthorized ( media-typed { "text/html" ( fn [req] ( unauthorized! ( -> req :resource :allowed? ) req )) "application/json" { :success false :message "Not authorized!" } :default ( constantly "Not authorized." ) } ) } )

friend-resource extends base-resource from above, just for fun. The unauthorized! function above is also mine; it pulls the ::friend/identity key out of the request, and also sends the function representing next step in the Liberator decision tree up to Friend's middleware. (If the user's not authenticated, this lets Friend workflows perform auth with a database, then jump BACK into Liberator's decision tree at the allowed? stage to try again. Pretty awesome.

That covers the Friend middleware integration. Now all we need to do is override :authorized? on each resource to return true or false, and everything else will just work. I wrote a few helpers that make it easy to test Friend's identity map in Liberator's authorized? function:

This resource extends the base resource, but adds in a default unauthorized handler. This is all Friend needs - if the user's unauthorized, either handle it immediately, OR, in the HTML case (assuming browsers always access via HTML), the resource throws the proper redirect.

Now all we need to do is override :authorized? on each resource to return true or false, and everything else will just work.

I wrote a helper function that defines nice authorization predicates based on Friend's concept of a role :

( defn roles "Returns an authorization predicate that checks if the authenticated user has the specified roles. (This is the usual friend behavior.)" [roles] ( fn [id] ( friend/authorized? roles id )))

This function creates a new base resource that extends friend-resource above, adding in the supplied authorization function:

( defn friend-auth "Returns a base resource that authenticates using the supplied auth-fn. Authorization failure will trigger Friend's default unauthorized response." [auth-fn] { :base friend-resource :authorized? auth-fn} )

Those two helpers work together to create Friend-aware (Friend-ly?) base resource generators. All resources that use these bases will be protected by the Friend middleware. In the example project, this means that they'll be protected with HTTP basic authentication, but you can add more workflows to perform different auth in a way that doesn't require you to rewrite your resources.

( defn role-auth "Returns a base resource that authenticates users against the supplied set of roles." [role-input] ( friend-auth ( comp ( roles role-input ) :request ))) ( def authenticated-base "Returns a base resource that authenticates users against the supplied set of roles." ( friend-auth ( comp boolean friend/identity :request )))

The first, role-auth , takes a set of roles and allows access to the resource if the authenticated user has a role that's in the set.

authenticated-base just checks that the user is authenticated (that the ::friend/identity key is present); no additional authorization comes into play.

The example project performs authentication using an in-memory "database":

( def users "dummy in-memory user database." { "root" { :username "root" :password ( creds/hash-bcrypt "admin_password" ) :roles #{ :admin }} "jane" { :username "jane" :password ( creds/hash-bcrypt "user_password" ) :roles #{ :user }}} )

Now, let's define some resources that use these helpers. These resources all use Friend for authorization. They allow, respectively, admins, users and any authenticated user.

( require '[liberator-friend.resources :as r :refer [defresource]] ) ( defresource admin-resource :base ( r/role-auth #{ :admin } ) :allowed-methods [ :get ] :available-media-types [ "text/plain" ] :handle-ok "Welcome, admin!" ) ( defresource user-resource :base ( r/role-auth #{ :user } ) :allowed-methods [ :get ] :available-media-types [ "text/plain" ] :handle-ok "Welcome, user!" ) ( defresource authenticated-resource :base r/authenticated-base :allowed-methods [ :get ] :available-media-types [ "text/plain" ] :handle-ok "Come on in. You're authenticated." )

Now we can serve these out using Compojure:

( defroutes site-routes ( GET "/" [] "Welcome to the liberator-friend demo site!" ) ( GET "/admin" [] admin-resource ) ( GET "/authenticated" [] authenticated-resource ) ( GET "/user" [] user-resource ))

Now let's hit the shell to test out the custom auth.