I am using Friend to build authentication into a Compojure web application.
I have defined a bespoke authentication workflow for Friend:
(defn authentication-workflow []
(routes
(GET "/logout" req
(friend/logout* {:status 200}))
(POST "/login" {{:keys [username password]} :params}
(if-let [user-record (authenticate-user username password)]
(workflows/make-auth user-record {:cemerick.friend/workflow :authorisation-workflow})
{:status 401}))))
The authentication part is factored out:
(defn authenticate-user [username password]
(if-let [user-record (get-user-for-username username)]
(if (creds/bcrypt-verify password (:password user-record))
(dissoc user-record :password))))
This works, but...
I am using AngularJS and having to post request parameters leads to some ugly Angular code (cribbed elsewhere from a StackOverflow answer):
$http({
method: 'POST',
url: '/login',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
transformRequest: function(obj) {
var str = [];
for (var p in obj)
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
return str.join("&");
},
data: {
username: username,
password: password
}
});
I would much rather do this much simpler call instead and just post a JSON object via the request body:
$http.post('/login', {username: username, password: password})
I tried to use ":body" in the authentication handler instead of ":params" but the value of :body seemed neither JSON nor Clojure to me so I don't know how I can use it:
{username [email protected], password password}
I already have JSON request/response mapping workflows working correctly for my REST API handlers, and I checked already that the request headers (e.g. ContentType) were correct for JSON.
So can this be done with Compojure/Friend, and if so how?
Here is some working code and an explanation...
First the Friend workflow, using the request body:
Second, the authentication function:
Third, the Compojure application with middlewares declared:
Finally a fragment of AngularJS code to post the credentials (username and password come from an AngularJS model):
So what happens is this...
The Angular javascript code posts JSON to the web application login URL. The "Content-Type" header is automatically set to "application/json" and the request body is automatically encoded as JSON, for example:
On the server, the middleware parses the JSON to a Clojure map and presents it to the handler via the ":body" keyword:
The request is then routed to the custom Friend authentication workflow.
Finally the submitted values are extracted from the Clojure map and used to authenticate the user.