I am trying to integrate Friend authentication and authorisation into a Clojure/Compojure single-page web application.
I have a login form backed by an Angular controller, and this controller uses AJAX to authenticate username and password against the web application and obtain an authenticated user record. Because of this, I do not want the default behaviour provided by the Friend form-based login - I basically want to rely on HTTP status codes and I do not want any of the Friend page-redirects.
For example, making an unauthenticated request should simply return a 401 status code, and should not redirect to "/login". I have this part working by specifying a custom ":unauthenticated-handler" when configuring Friend (code included below).
On a successful login I simply want a 200 status code, and not a redirect to the originally requested page. This is what I can't get working.
I wrote a custom Friend authentication workflow based on the various examples (my Clojure skills are beginner level right now):
(defn my-auth
[& {:keys [credential-fn]}]
(routes
(GET "/logout" req
(friend/logout* {:status 200}))
(POST "/login" {{:keys [username password]} :params}
(if-let [user-record (-> username credential-fn)]
(if
(and
[user-record password]
(creds/bcrypt-verify password (:password user-record)))
(let [user-record (dissoc user-record :password)]
(workflows/make-auth user-record {:cemerick.friend/workflow :my-auth :cemerick.friend/redirect-on-auth? true}))
{:status 401})
{:status 401}))))
Here is my handler with middlewares declared:
(def app
(->
(handler/site
(friend/authenticate app-routes
{:credential-fn (partial creds/bcrypt-credential-fn my-credential-fn)
:unauthenticated-handler unauthenticated
:workflows [(my-auth :credential-fn my-credential-fn)]}))
(session/wrap-session)
(params/wrap-keyword-params)
(json/wrap-json-body)
(json/wrap-json-response {:pretty true})))
And the additional handler function referenced by the above:
(defn unauthenticated [v]
{:status 401 :body "Unauthenticated"})
Finally an additional routes fragment to test the authentication:
(GET "/auth" req
(friend/authenticated (str "You have successfully authenticated as "
(friend/current-authentication))))
This mostly works, and almost does everything I need.
Because "redirect-on-auth?" is true in "make-auth", on a successful login a page-redirect is generated - I want to prevent that redirect so I set the value to false. However, this single change results in a 404 and a failed login.
So as well as the Friend authentication map I need somehow to return a 200 status code here, and also I want to return the "user-record" in the response body so the client application can tailor the UI depending on the user roles (I already have JSON requests/responses wrapped and working).
So I think I need the equivalent of this when I invoke the Friend "make-auth" function:
{:status 200 :body user-record}
However it seems like I can have either the authentication map, or the response - but not both together.
Can this be achieved with Friend and if so how?